From f52f999c09fe9f4eecbec1991881ca873bba467f Mon Sep 17 00:00:00 2001 From: SaW Date: Tue, 28 Jan 2025 09:08:21 +0100 Subject: [PATCH] Refactor of the CheckMountStateAction class (#919) * Update CheckMountStateAction.cpp The refactoring focused on improving readability, reducing redundancy, and optimizing performance where possible. * Update CheckMountStateAction.h * Update CheckMountStateAction.cpp * Update CheckMountStateAction.cpp * Update CheckMountStateAction.cpp * Update CheckMountStateAction.h * Fix lowest level mount from config * Properly fix * Reduced masterInShapeshiftForm lookups to 1 Reduced masterInShapeshiftForm lookups to 1 * Fix for missing useFastGroundMountAtMinLevel * Final commit: Ensure max 99 speed in BG --- .../actions/CheckMountStateAction.cpp | 426 ++++++++++-------- src/strategy/actions/CheckMountStateAction.h | 15 + 2 files changed, 241 insertions(+), 200 deletions(-) diff --git a/src/strategy/actions/CheckMountStateAction.cpp b/src/strategy/actions/CheckMountStateAction.cpp index 62d32020..fbddbb21 100644 --- a/src/strategy/actions/CheckMountStateAction.cpp +++ b/src/strategy/actions/CheckMountStateAction.cpp @@ -4,7 +4,6 @@ */ #include "CheckMountStateAction.h" - #include "BattlegroundWS.h" #include "Event.h" #include "PlayerbotAI.h" @@ -15,162 +14,115 @@ bool CheckMountStateAction::Execute(Event event) { - bool noattackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count"); + bool noAttackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count"); bool enemy = AI_VALUE(Unit*, "enemy player target"); bool dps = AI_VALUE(Unit*, "dps target"); bool shouldDismount = false; bool shouldMount = false; - if (Unit* currentTarget = AI_VALUE(Unit*, "current target")) + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (currentTarget) { - float dismount_distance; - float mount_distance; - if (PlayerbotAI::IsMelee(bot)) - { - dismount_distance = sPlayerbotAIConfig->meleeDistance + 2.0f; - mount_distance = sPlayerbotAIConfig->meleeDistance + 10.0f; - } - else - { - dismount_distance = sPlayerbotAIConfig->spellDistance + 2.0f; - mount_distance = sPlayerbotAIConfig->spellDistance + 10.0f; - } - - // warrior bots should dismount far enough to charge (because its important for generating some initial rage), - // a real player would be riding toward enemy mashing the charge key but the bots wont cast charge while mounted - if (CLASS_WARRIOR == bot->getClass()) - dismount_distance = std::max(18.0f, dismount_distance); - - // mount_distance should be >= 21 regardless of class, because when travelling a distance < 21 it takes longer - // to cast mount-spell than the time saved from the speed increase. At a distance of 21 both approaches take 3 - // seconds: - // 21 / 7 = 21 / 14 + 1.5 = 3 (7 = dismounted speed 14 = epic-mount speed 1.5 = mount-spell cast time) - mount_distance = std::max(21.0f, mount_distance); + float dismountDistance = CalculateDismountDistance(); + float mountDistance = CalculateMountDistance(); float combatReach = bot->GetCombatReach() + currentTarget->GetCombatReach(); - float disToTarget = bot->GetExactDist(currentTarget); - shouldDismount = disToTarget <= dismount_distance + combatReach; - shouldMount = disToTarget > mount_distance + combatReach; + float distanceToTarget = bot->GetExactDist(currentTarget); + shouldDismount = distanceToTarget <= dismountDistance + combatReach; + shouldMount = distanceToTarget > mountDistance + combatReach; } else { - shouldDismount = false; shouldMount = true; } if (bot->IsMounted() && shouldDismount) { - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + Dismount(); return true; } + // If there is a master and bot not in BG Player* master = GetMaster(); - if (master != nullptr && !bot->InBattleground()) + if (master && !bot->InBattleground()) { + masterInShapeshiftForm = master->GetShapeshiftForm(); + if (!bot->GetGroup() || bot->GetGroup()->GetLeaderGUID() != master->GetGUID()) return false; - auto masterInShapeshiftForm = master->GetShapeshiftForm(); - - // bool farFromMaster = sServerFacade->GetDistance2d(bot, master) > sPlayerbotAIConfig->sightDistance; - if ((master->IsMounted() || masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC || masterInShapeshiftForm == FORM_TRAVEL) - && !bot->IsMounted() && noattackers && shouldMount && !bot->IsInCombat() && botAI->GetState() != BOT_STATE_COMBAT) - { + if (ShouldFollowMasterMountState(master, noAttackers, shouldMount)) return Mount(); - } - if ((!master->IsMounted() && masterInShapeshiftForm != FORM_FLIGHT && masterInShapeshiftForm != FORM_FLIGHT_EPIC && masterInShapeshiftForm != FORM_TRAVEL) - && bot->IsMounted()) + if (ShouldDismountForMaster(master)) { - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + Dismount(); return true; } return false; } - // For random bots - if (!bot->InBattleground() && !master) + // If there is no master and bot not in BG + if (!master && !bot->InBattleground()) { - if (!bot->IsMounted() && noattackers && shouldMount && !bot->IsInCombat()) + if (!bot->IsMounted() && noAttackers && shouldMount && !bot->IsInCombat()) { return Mount(); } } - if (bot->InBattleground() && shouldMount && noattackers && !bot->IsInCombat() && !bot->IsMounted()) + // If the bot is in BG + if (bot->InBattleground() && shouldMount && noAttackers && !bot->IsInCombat() && !bot->IsMounted()) { + // WSG Specific - Do not mount when carrying the flag if (bot->GetBattlegroundTypeId() == BATTLEGROUND_WS) { - BattlegroundWS* bg = (BattlegroundWS*)botAI->GetBot()->GetBattleground(); if (bot->HasAura(23333) || bot->HasAura(23335)) { return false; } } - return Mount(); } - if (!bot->IsFlying() && shouldDismount && bot->IsMounted() && (enemy || dps || (!noattackers && bot->IsInCombat()))) + if (!bot->IsFlying() && shouldDismount && bot->IsMounted() && (enemy || dps || (!noAttackers && bot->IsInCombat()))) { - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + Dismount(); return true; } - + return false; } bool CheckMountStateAction::isUseful() { - // do not use on vehicle - if (botAI->IsInVehicle()) + if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || !bot->IsOutdoors() || bot->InArena()) return false; - if (bot->isDead()) - return false; - - if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT)) - return false; - - if (!bot->IsOutdoors()) - return false; - - // in addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will + // In addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will // cause bot to falsly indicate they are outdoors. This fixes bug where bot tries to mount indoors (which seems // to mostly be an issue in tunnels of WSG and AV) - if (!bot->IsMounted() && bot->GetPositionZ() < bot->GetMapWaterOrGroundLevel( - bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())) - return false; - - if (bot->InArena()) + if (!bot->IsMounted() && bot->GetPositionZ() < bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())) return false; if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted()) return false; - bool firstmount = bot->GetLevel() >= sPlayerbotAIConfig->useGroundMountAtMinLevel; - if (!firstmount) + // Do not mount when level lower than minimum required + if (bot->GetLevel() < sPlayerbotAIConfig->useGroundMountAtMinLevel) return false; // Do not use with BG Flags if (bot->HasAura(23333) || bot->HasAura(23335) || bot->HasAura(34976)) - { return false; - } // Only mount if BG starts in less than 30 sec if (bot->InBattleground()) { if (Battleground* bg = bot->GetBattleground()) - if (bg->GetStatus() == STATUS_WAIT_JOIN) - { - if (bg->GetStartDelayTime() > BG_START_DELAY_30S) - return false; - } + if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S) + return false; } return true; @@ -178,40 +130,98 @@ bool CheckMountStateAction::isUseful() bool CheckMountStateAction::Mount() { - uint32 secondmount = sPlayerbotAIConfig->useFastGroundMountAtMinLevel; - if (bot->isMoving()) - { bot->StopMoving(); - // bot->GetMotionMaster()->Clear(); - // bot->GetMotionMaster()->MoveIdle(); - } Player* master = GetMaster(); botAI->RemoveShapeshift(); botAI->RemoveAura("tree of life"); - int32 masterSpeed = 59; - int32 masterMountType = 0; - SpellInfo const* masterSpell = nullptr; + int32 masterSpeed = CalculateMasterMountSpeed(master); + bool hasSwiftMount = CheckForSwiftMount(); + + auto allSpells = GetAllMountSpells(); + int32 masterMountType = GetMountType(master); + + if (TryPreferredMount(master)) + return true; + + auto& spells = allSpells[masterMountType]; + if (hasSwiftMount) + FilterMountsBySpeed(spells, masterSpeed); + + // No preferred mount found (or invalid), continue with random mount selection + if (TryRandomMount(spells)) + return true; + + std::vector items = AI_VALUE2(std::vector, "inventory items", "mount"); + if (!items.empty()) + return UseItemAuto(*items.begin()); + + return false; +} + +float CheckMountStateAction::CalculateDismountDistance() const +{ + // Warrior bots should dismount far enough to charge (because its important for generating some initial rage), + // a real player would be riding toward enemy mashing the charge key but the bots wont cast charge while mounted + bool isMelee = PlayerbotAI::IsMelee(bot); + float dismountDistance = isMelee ? sPlayerbotAIConfig->meleeDistance + 2.0f : sPlayerbotAIConfig->spellDistance + 2.0f; + return bot->getClass() == CLASS_WARRIOR ? std::max(18.0f, dismountDistance) : dismountDistance; +} + +float CheckMountStateAction::CalculateMountDistance() const +{ + // Mount distance should be >= 21 regardless of class, because when travelling a distance < 21 it takes longer + // to cast mount-spell than the time saved from the speed increase. At a distance of 21 both approaches take 3 + // seconds: + // 21 / 7 = 21 / 14 + 1.5 = 3 (7 = dismounted speed 14 = epic-mount speed 1.5 = mount-spell cast time) + bool isMelee = PlayerbotAI::IsMelee(bot); + return std::max(21.0f, isMelee ? sPlayerbotAIConfig->meleeDistance + 10.0f : sPlayerbotAIConfig->spellDistance + 10.0f); +} + +void CheckMountStateAction::Dismount() +{ + WorldPacket emptyPacket; + bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); +} + +bool CheckMountStateAction::ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const +{ + bool isMasterMounted = master->IsMounted() || masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC || masterInShapeshiftForm == FORM_TRAVEL; + return isMasterMounted && !bot->IsMounted() && noAttackers && shouldMount && !bot->IsInCombat() && botAI->GetState() != BOT_STATE_COMBAT; +} + +bool CheckMountStateAction::ShouldDismountForMaster(Player* master) const +{ + bool isMasterMounted = master->IsMounted() || masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC || masterInShapeshiftForm == FORM_TRAVEL; + return !isMasterMounted && bot->IsMounted(); +} + +int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master) const +{ + if (bot->GetPureSkillValue(SKILL_RIDING) <= 75 && bot->GetLevel() < sPlayerbotAIConfig->useFastGroundMountAtMinLevel) + return 59; + + // If there ia a master and bot not in BG if (master != nullptr && !bot->InBattleground()) { - auto masterInShapeshiftForm = master->GetShapeshiftForm(); + auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); + if (!auraEffects.empty()) + { + SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo(); + return std::max(masterSpell->Effects[1].BasePoints, masterSpell->Effects[2].BasePoints); + } - if (!master->GetAuraEffectsByType(SPELL_AURA_MOUNTED).empty()) - { - masterSpell = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED).front()->GetSpellInfo(); - masterSpeed = std::max(masterSpell->Effects[1].BasePoints, masterSpell->Effects[2].BasePoints); - } - else if (masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC) - { - masterMountType = 1; - masterSpeed = (masterInShapeshiftForm == FORM_FLIGHT_EPIC) ? 279 : 149; - } + else if (masterInShapeshiftForm == FORM_FLIGHT_EPIC) + return 279; + + else if (masterInShapeshiftForm == FORM_FLIGHT) + return 149; } else + // Bots on their own { - masterSpeed = 59; for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) { uint32 spellId = itr->first; @@ -222,36 +232,54 @@ bool CheckMountStateAction::Mount() if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active || spellInfo->IsPassive()) continue; - int32 effect = std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints); - if (effect > masterSpeed) - masterSpeed = effect; + int32 speed = std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints); + + if (speed > 59) + { + // Ensure max speed of 99 in BG + if (bot->InBattleground()) + return (speed > 99) ? 99 : speed; + + return speed; + } } } - if (bot->GetPureSkillValue(SKILL_RIDING) <= 75 && bot->GetLevel() < secondmount) - masterSpeed = 59; + return 59; +} - if (bot->InBattleground() && masterSpeed > 99) - masterSpeed = 99; - - bool hasSwiftMount = false; - - // std::map > spells; - std::map>> allSpells; - for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) +bool CheckMountStateAction::CheckForSwiftMount() const +{ + for (auto& [spellId, spellState] : bot->GetSpellMap()) { - uint32 spellId = itr->first; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo || spellInfo->Effects[0].ApplyAuraName != SPELL_AURA_MOUNTED) continue; - if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active || spellInfo->IsPassive()) + if (spellState->State == PLAYERSPELL_REMOVED || !spellState->Active || spellInfo->IsPassive()) continue; int32 effect = std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints); - // if (effect < masterSpeed) - // continue; + if ((effect > 59 && spellInfo->Effects[1].ApplyAuraName != SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || + (effect > 149 && spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED)) + return true; + } + return false; +} +std::map>> CheckMountStateAction::GetAllMountSpells() const +{ + std::map>> allSpells; + for (auto& [spellId, spellState] : bot->GetSpellMap()) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo || spellInfo->Effects[0].ApplyAuraName != SPELL_AURA_MOUNTED) + continue; + + if (spellState->State == PLAYERSPELL_REMOVED || !spellState->Active || spellInfo->IsPassive()) + continue; + + int32 effect = std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints); uint32 index = (spellInfo->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED || spellInfo->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED || // Winged Steed of the Ebon Blade @@ -260,102 +288,100 @@ bool CheckMountStateAction::Mount() // This incorrectly gets categorised as a ground mount, force this to flyer only. // TODO: Add other scaling mounts here if they have the same issue, or adjust above // checks so that they are all correctly detected. - spellInfo->Id == 54729) - ? 1 // Flying Mount - : 0; // Ground Mount - - if (index == 0 && - std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) > 59) - hasSwiftMount = true; - - if (index == 1 && - std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) > 149) - hasSwiftMount = true; + spellInfo->Id == 54729) ? 1 : 0; allSpells[index][effect].push_back(spellId); } + return allSpells; +} - if (masterSpell) +bool CheckMountStateAction::TryPreferredMount(Player* master) const +{ + static bool tableExists = false; + static bool tableChecked = false; + + if (!tableChecked) { - masterMountType = (masterSpell->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED || - masterSpell->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) - ? 1 - : 0; + // Check for preferred mounts table in db + QueryResult checkTable = PlayerbotsDatabase.Query( + "SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_schema = 'acore_playerbots' AND table_name = 'playerbots_preferred_mounts')"); + tableExists = checkTable && checkTable->Fetch()[0].Get() == 1; + tableChecked = true; } - // Check for preferred mounts table in db - QueryResult checkTable = PlayerbotsDatabase.Query( - "SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_schema = 'acore_playerbots' AND table_name = 'playerbots_preferred_mounts')"); - - if (checkTable) + if (tableExists) { - uint32 tableExists = checkTable->Fetch()[0].Get(); - if (tableExists == 1) - { - // Check for preferred mount entry - QueryResult result = PlayerbotsDatabase.Query( + // Check for preferred mount entry + QueryResult result = PlayerbotsDatabase.Query( "SELECT spellid FROM playerbots_preferred_mounts WHERE guid = {} AND type = {}", - bot->GetGUID().GetCounter(), masterMountType); + bot->GetGUID().GetCounter(), GetMountType(master)); - if (result) - { - std::vector mounts; - do - { - Field* fields = result->Fetch(); - uint32 spellId = fields[0].Get(); - mounts.push_back(spellId); - } while (result->NextRow()); - - uint32 index = urand(0, mounts.size() - 1); - // Validate spell ID - if (index < mounts.size() && sSpellMgr->GetSpellInfo(mounts[index])) - { - // TODO: May want to do checks for 'bot riding skill > skill required to ride the mount' - return botAI->CastSpell(mounts[index], bot); - } - } - } - } - - // No preferred mount found (or invalid), continue with random mount selection - std::map>& spells = allSpells[masterMountType]; - if (hasSwiftMount) - { - for (auto i : spells) + if (result) { - std::vector ids = i.second; - for (auto itr : ids) + std::vector mounts; + do { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr); - if (!spellInfo) - continue; + mounts.push_back(result->Fetch()[0].Get()); + } while (result->NextRow()); - if (masterMountType == 0 && masterSpeed > 59 && - std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) < 99) - spells[59].clear(); - - if (masterMountType == 1 && masterSpeed > 149 && - std::max(spellInfo->Effects[EFFECT_1].BasePoints, spellInfo->Effects[EFFECT_2].BasePoints) < 279) - spells[149].clear(); - } + // Validate spell ID + // TODO: May want to do checks for 'bot riding skill > skill required to ride the mount' + uint32 index = urand(0, mounts.size() - 1); + if (index < mounts.size() && sSpellMgr->GetSpellInfo(mounts[index])) + return botAI->CastSpell(mounts[index], bot); } } - - for (std::map>::iterator i = spells.begin(); i != spells.end(); ++i) - { - std::vector& ids = i->second; - uint32 index = urand(0, ids.size() - 1); - if (index >= ids.size()) - continue; - - return botAI->CastSpell(ids[index], bot); - ; - } - - std::vector items = AI_VALUE2(std::vector, "inventory items", "mount"); - if (!items.empty()) - return UseItemAuto(*items.begin()); - + return false; +} + +uint32 CheckMountStateAction::GetMountType(Player* master) const +{ + if (!master) + return 0; + + auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED); + + if (!auraEffects.empty()) + { + SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo(); + return (masterSpell->Effects[1].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED || + masterSpell->Effects[2].ApplyAuraName == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) ? 1 : 0; + } + else if (masterInShapeshiftForm == FORM_FLIGHT || masterInShapeshiftForm == FORM_FLIGHT_EPIC) + return 1; + + return 0; +} + +void CheckMountStateAction::FilterMountsBySpeed(std::map>& spells, int32 masterSpeed) const +{ + for (auto& [speed, ids] : spells) + { + for (auto& id : ids) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(id); + if (!spellInfo) + continue; + + if (masterSpeed > 59 && std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints) < 99) + spells[59].clear(); + + if (masterSpeed > 149 && std::max(spellInfo->Effects[1].BasePoints, spellInfo->Effects[2].BasePoints) < 279) + spells[149].clear(); + } + } +} + +bool CheckMountStateAction::TryRandomMount(const std::map>& spells) const +{ + for (const auto& [speed, ids] : spells) + { + if (ids.empty()) + continue; + + uint32 index = urand(0, ids.size() - 1); + if (index < ids.size()) + return botAI->CastSpell(ids[index], bot); + } return false; } diff --git a/src/strategy/actions/CheckMountStateAction.h b/src/strategy/actions/CheckMountStateAction.h index 9acb3bd7..4e377e6d 100644 --- a/src/strategy/actions/CheckMountStateAction.h +++ b/src/strategy/actions/CheckMountStateAction.h @@ -19,6 +19,21 @@ public: bool isUseful() override; bool isPossible() override { return true; } bool Mount(); + +private: + ShapeshiftForm masterInShapeshiftForm; + float CalculateDismountDistance() const; + float CalculateMountDistance() const; + void Dismount(); + bool ShouldFollowMasterMountState(Player* master, bool noAttackers, bool shouldMount) const; + bool ShouldDismountForMaster(Player* master) const; + int32 CalculateMasterMountSpeed(Player* master) const; + bool CheckForSwiftMount() const; + std::map>> GetAllMountSpells() const; + bool TryPreferredMount(Player* master) const; + uint32 GetMountType(Player* master) const; + void FilterMountsBySpeed(std::map>& spells, int32 masterSpeed) const; + bool TryRandomMount(const std::map>& spells) const; }; #endif