diff --git a/src/strategy/actions/CheckMountStateAction.cpp b/src/strategy/actions/CheckMountStateAction.cpp index 0f37a6da..2fe0aba3 100644 --- a/src/strategy/actions/CheckMountStateAction.cpp +++ b/src/strategy/actions/CheckMountStateAction.cpp @@ -49,64 +49,7 @@ MountData CollectMountData(const Player* bot) return data; } -bool CheckMountStateAction::isUseful() -{ - // Not useful when: - if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || - !bot->IsOutdoors() || bot->InArena()) - return false; - - master = GetMaster(); - - // Get shapeshift states, only applicable when there's a master - if (master) - { - botInShapeshiftForm = bot->GetShapeshiftForm(); - masterInShapeshiftForm = master->GetShapeshiftForm(); - } - - // Not useful when in combat and not currently mounted / travel formed - if ((bot->IsInCombat() || botAI->GetState() == BOT_STATE_COMBAT) && - !bot->IsMounted() && botInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT_EPIC) - return false; - - // 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) - float posZ = bot->GetPositionZ(); - float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ); - if (!bot->IsMounted() && posZ < groundLevel) - return false; - - // Not useful when bot does not have mount strat and is not currently mounted - if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted()) - return false; - - // Not useful when level lower than minimum required - if (bot->GetLevel() < sPlayerbotAIConfig->useGroundMountAtMinLevel) - return false; - - // Allow mounting while transformed only if the form allows it - if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm()) - return false; - - // BG Logic - if (bot->InBattleground()) - { - // Do not use when carrying 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 (Battleground* bg = bot->GetBattleground()) - if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S) - return false; - } - - return true; -} - -bool CheckMountStateAction::Execute(Event /*event*/) +bool CheckMountStateAction::Execute(Event event) { // Determine if there are no attackers bool noAttackers = !AI_VALUE2(bool, "combat", "self target") || !AI_VALUE(uint8, "attacker count"); @@ -120,9 +63,10 @@ bool CheckMountStateAction::Execute(Event /*event*/) { float dismountDistance = CalculateDismountDistance(); float mountDistance = CalculateMountDistance(); + + // Cache combat reach and distance calculations float combatReach = bot->GetCombatReach() + currentTarget->GetCombatReach(); float distanceToTarget = bot->GetExactDist(currentTarget); - shouldDismount = (distanceToTarget <= dismountDistance + combatReach); shouldMount = (distanceToTarget > mountDistance + combatReach); } @@ -131,20 +75,14 @@ bool CheckMountStateAction::Execute(Event /*event*/) shouldMount = true; } - // If should dismount, or master (if any) is no longer in travel form, yet bot still is, remove the shapeshifts - if (shouldDismount || - (masterInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm == FORM_TRAVEL) || - (masterInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm == FORM_FLIGHT && master && !master->IsMounted()) || - (masterInShapeshiftForm != FORM_FLIGHT_EPIC && botInShapeshiftForm == FORM_FLIGHT_EPIC && master && !master->IsMounted())) - botAI->RemoveShapeshift(); - - if (shouldDismount && bot->IsMounted()) + if (bot->IsMounted() && shouldDismount) { Dismount(); return true; } // If there is a master and bot not in BG + Player* master = GetMaster(); bool inBattleground = bot->InBattleground(); if (master && !inBattleground) { @@ -152,10 +90,12 @@ bool CheckMountStateAction::Execute(Event /*event*/) if (!group || group->GetLeaderGUID() != master->GetGUID()) return false; + masterInShapeshiftForm = master->GetShapeshiftForm(); + if (ShouldFollowMasterMountState(master, noAttackers, shouldMount)) return Mount(); - else if (ShouldDismountForMaster(master) && bot->IsMounted()) + if (ShouldDismountForMaster(master)) { Dismount(); return true; @@ -179,31 +119,63 @@ bool CheckMountStateAction::Execute(Event /*event*/) return false; } -bool CheckMountStateAction::Mount() +bool CheckMountStateAction::isUseful() { - // Remove current Shapeshift if need be - if (botInShapeshiftForm != FORM_TRAVEL && - botInShapeshiftForm != FORM_FLIGHT && - botInShapeshiftForm != FORM_FLIGHT_EPIC) + if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || + !bot->IsOutdoors() || bot->InArena()) + return false; + + // 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) + float posZ = bot->GetPositionZ(); + float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ); + if (!bot->IsMounted() && posZ < groundLevel) + return false; + + if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted()) + return false; + + // Do not mount when level lower than minimum required + if (bot->GetLevel() < sPlayerbotAIConfig->useGroundMountAtMinLevel) + return false; + + // Allow mounting while transformed only if the form allows it + if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm()) + return false; + + // BG Logic + if (bot->InBattleground()) { - botAI->RemoveShapeshift(); - botAI->RemoveAura("tree of life"); + // Do not use when carrying 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 (Battleground* bg = bot->GetBattleground()) + if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S) + return false; } - // Disabled for now until properly implemented - //if (TryPreferredMount(master)) - // return true; + return true; +} + +bool CheckMountStateAction::Mount() +{ + if (bot->isMoving()) + bot->StopMoving(); + + Player* master = GetMaster(); + botAI->RemoveShapeshift(); + botAI->RemoveAura("tree of life"); - // Get bot mount data MountData mountData = CollectMountData(bot); - int32 masterMountType = GetMountType(master); int32 masterSpeed = CalculateMasterMountSpeed(master, mountData); - // Try shapeshift - if (TryForms(master, masterMountType, masterSpeed)) + if (TryPreferredMount(master)) return true; - // Try random mount + int32 masterMountType = GetMountType(master); auto spellsIt = mountData.allSpells.find(masterMountType); if (spellsIt != mountData.allSpells.end()) { @@ -219,131 +191,6 @@ bool CheckMountStateAction::Mount() return false; } -void CheckMountStateAction::Dismount() -{ - if (bot->isMoving()) - bot->StopMoving(); - - WorldPacket emptyPacket; - bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); -} - -bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const -{ - if (!master) - return false; - - // Check if master is in Travel Form and bot can do the same - if (botAI->CanCastSpell(SPELL_TRAVEL_FORM, bot, true) && - masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm != FORM_TRAVEL) - { - botAI->CastSpell(SPELL_TRAVEL_FORM, bot); - return true; - } - - // Check if master is in Flight Form or has a flying mount and bot can flight form - if (botAI->CanCastSpell(SPELL_FLIGHT_FORM, bot, true) && - ((masterInShapeshiftForm == FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT) || - (masterMountType == 1 && masterSpeed == 149))) - { - botAI->CastSpell(SPELL_FLIGHT_FORM, bot); - - // Compensate speedbuff - bot->SetSpeed(MOVE_RUN, 2.5, true); - return true; - } - - // Check if master is in Swift Flight Form or has an epic flying mount and bot can swift flight form - if (botAI->CanCastSpell(SPELL_SWIFT_FLIGHT_FORM, bot, true) && - ((masterInShapeshiftForm == FORM_FLIGHT_EPIC && botInShapeshiftForm != FORM_FLIGHT_EPIC) || - (masterMountType == 1 && masterSpeed == 279))) - { - botAI->CastSpell(SPELL_SWIFT_FLIGHT_FORM, bot); - - // Compensate speedbuff - bot->SetSpeed(MOVE_RUN, 3.8, true); - return true; - } - - return false; -} - -bool CheckMountStateAction::TryPreferredMount(Player* master) const -{ - static bool tableExists = false; - static bool tableChecked = false; - - if (!tableChecked) - { - // 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; - } - - if (tableExists) - { - // Check for preferred mount entry - QueryResult result = PlayerbotsDatabase.Query( - "SELECT spellid FROM playerbots_preferred_mounts WHERE guid = {} AND type = {}", - bot->GetGUID().GetCounter(), GetMountType(master)); - - if (result) - { - std::vector mounts; - do - { - mounts.push_back(result->Fetch()[0].Get()); - } while (result->NextRow()); - - // Validate spell ID - // TODO: May want to do checks for 'bot riding skill > skill required to ride the mount' - if (!mounts.empty()) - { - uint32 index = urand(0, mounts.size() - 1); - if (index < mounts.size() && sSpellMgr->GetSpellInfo(mounts[index]) && - botAI->CanCastSpell(mounts[index], bot)) - { - if (bot->isMoving()) - bot->StopMoving(); - - botAI->CastSpell(mounts[index], bot); - return true; - } - } - } - } - return false; -} - -bool CheckMountStateAction::TryRandomMountFiltered(const std::map>& spells, int32 masterSpeed) const -{ - for (const auto& pair : spells) - { - int32 currentSpeed = pair.first; - if ((masterSpeed > 59 && currentSpeed < 99) || (masterSpeed > 149 && currentSpeed < 279)) - continue; - - // Pick a random mount from the candidate group. - const auto& ids = pair.second; - if (!ids.empty()) - { - uint32 index = urand(0, ids.size() - 1); - - if (botAI->CanCastSpell(ids[index], bot)) - { - if (bot->isMoving()) - bot->StopMoving(); - - botAI->CastSpell(ids[index], bot); - return true; - } - } - } - return false; -} - float CheckMountStateAction::CalculateDismountDistance() const { // Warrior bots should dismount far enough to charge (because it's important for generating some initial rage), @@ -364,6 +211,12 @@ float CheckMountStateAction::CalculateMountDistance() const return std::max(21.0f, baseDistance); } +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 || @@ -437,3 +290,65 @@ uint32 CheckMountStateAction::GetMountType(Player* master) const return 0; } + +bool CheckMountStateAction::TryPreferredMount(Player* master) const +{ + static bool tableExists = false; + static bool tableChecked = false; + + if (!tableChecked) + { + // 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; + } + + if (tableExists) + { + // Check for preferred mount entry + QueryResult result = PlayerbotsDatabase.Query( + "SELECT spellid FROM playerbots_preferred_mounts WHERE guid = {} AND type = {}", + bot->GetGUID().GetCounter(), GetMountType(master)); + + if (result) + { + std::vector mounts; + do + { + mounts.push_back(result->Fetch()[0].Get()); + } while (result->NextRow()); + + // Validate spell ID + // TODO: May want to do checks for 'bot riding skill > skill required to ride the mount' + if (!mounts.empty()) + { + uint32 index = urand(0, mounts.size() - 1); + if (index < mounts.size() && sSpellMgr->GetSpellInfo(mounts[index])) + return botAI->CastSpell(mounts[index], bot); + } + } + } + return false; +} + +bool CheckMountStateAction::TryRandomMountFiltered(const std::map>& spells, int32 masterSpeed) const +{ + // Iterate over each speed group once. + for (const auto& pair : spells) + { + int32 currentSpeed = pair.first; + if ((masterSpeed > 59 && currentSpeed < 99) || (masterSpeed > 149 && currentSpeed < 279)) + continue; + + const auto& ids = pair.second; + if (!ids.empty()) + { + // Pick a random mount from the candidate group. + uint32 index = urand(0, ids.size() - 1); + return botAI->CastSpell(ids[index], bot); + } + } + return false; +} diff --git a/src/strategy/actions/CheckMountStateAction.h b/src/strategy/actions/CheckMountStateAction.h index b40958c5..affba993 100644 --- a/src/strategy/actions/CheckMountStateAction.h +++ b/src/strategy/actions/CheckMountStateAction.h @@ -8,10 +8,6 @@ #include "UseItemAction.h" -const uint16 SPELL_TRAVEL_FORM = 783; -const uint16 SPELL_FLIGHT_FORM = 33943; -const uint16 SPELL_SWIFT_FLIGHT_FORM = 40120; - struct MountData { bool swiftMount = false; @@ -34,9 +30,7 @@ public: bool Mount(); private: - Player* master; ShapeshiftForm masterInShapeshiftForm; - ShapeshiftForm botInShapeshiftForm; float CalculateDismountDistance() const; float CalculateMountDistance() const; void Dismount(); @@ -45,7 +39,6 @@ private: int32 CalculateMasterMountSpeed(Player* master, const MountData& mountData) const; bool CheckForSwiftMount() const; std::map>> GetAllMountSpells() const; - bool TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const; bool TryPreferredMount(Player* master) const; uint32 GetMountType(Player* master) const; bool TryRandomMountFiltered(const std::map>& spells, int32 masterSpeed) const;