From 6effabfa42a2f793c417dfdfff948736430adae2 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 15 Nov 2025 18:19:16 +0100 Subject: [PATCH] Core - Fix Bots can pickup the flag in Eye of the Strom instantly, without cast time from the spawn point (#1704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Eye of the Storm Flag Capture Behavior   ## Previous Behavior - Bots used to interact with the Netherstorm flag the moment they reached interaction range, leading to instant pickups even before they were correctly positioned. - They did not respect the requirement to stand within the capture ring or dismount before channeling, so the interaction finished immediately without the intended delay.   ## Current Behavior - Bots now verify they are inside the Eye of the Storm capture circle and will reposition toward the center flag or base flag until they are within 2.5 yards of the game object before starting the channel. - Once inside the circle they dismount, drop shapeshift forms, and come to a full stop before beginning the channel cast. - When channeling the center flag capture spell, bots keep checking for the ongoing `SPELL_CAPTURE_BANNER` cast and wait for it to finish instead of attempting repeated instant interactions. - They will be interrupted if they receice any damage   These adjustments align the Eye of the Storm flow with the retail mechanics and prevent bots from taking the flag instantly when it spawns. Fixes #1700. --- src/strategy/actions/BattleGroundTactics.cpp | 179 ++++++++++++++++--- 1 file changed, 151 insertions(+), 28 deletions(-) diff --git a/src/strategy/actions/BattleGroundTactics.cpp b/src/strategy/actions/BattleGroundTactics.cpp index db5235f5..827ab019 100644 --- a/src/strategy/actions/BattleGroundTactics.cpp +++ b/src/strategy/actions/BattleGroundTactics.cpp @@ -4,10 +4,12 @@ */ #include "BattleGroundTactics.h" -#include "BattleGroundJoinAction.h" + +#include #include "ArenaTeam.h" #include "ArenaTeamMgr.h" +#include "BattleGroundJoinAction.h" #include "Battleground.h" #include "BattlegroundAB.h" #include "BattlegroundAV.h" @@ -22,11 +24,12 @@ #include "BattlegroundSA.h" #include "BattlegroundWS.h" #include "Event.h" +#include "GameObject.h" #include "IVMapMgr.h" +#include "PathGenerator.h" #include "Playerbots.h" #include "PositionValue.h" #include "PvpTriggers.h" -#include "PathGenerator.h" #include "ServerFacade.h" #include "Vehicle.h" @@ -1754,7 +1757,7 @@ bool BGTactics::moveToStart(bool force) WS_WAITING_POS_ALLIANCE_2.GetPositionY() + frand(-4.0f, 4.0f), WS_WAITING_POS_ALLIANCE_2.GetPositionZ()); } - else // BB_WSG_WAIT_SPOT_SPAWN + else // BB_WSG_WAIT_SPOT_SPAWN { if (bot->GetTeamId() == TEAM_HORDE) MoveTo(bg->GetMapId(), WS_WAITING_POS_HORDE_3.GetPositionX() + frand(-10.0f, 10.0f), @@ -3365,12 +3368,12 @@ bool BGTactics::resetObjective() return false; // Adjust role-change chance based on battleground type - uint32 oddsToChangeRole = 1; // default low + uint32 oddsToChangeRole = 1; // default low BattlegroundTypeId bgType = bg->GetBgTypeID(); if (bgType == BATTLEGROUND_WS) oddsToChangeRole = 2; - else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB) + else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB) oddsToChangeRole = 1; else if (bgType == BATTLEGROUND_AV) oddsToChangeRole = 0; @@ -3578,6 +3581,16 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vector(bg); + if (eyeBg) + eyCenterFlag = eyeBg->GetBGObject(BG_EY_OBJECT_FLAG_NETHERSTORM); + } + // Set up appropriate search ranges and object lists based on BG type switch (bgType) { @@ -3607,27 +3620,82 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetCurrentSpell(spellType); + if (!currentSpell || !currentSpell->m_spellInfo || currentSpell->m_spellInfo->Id != SPELL_CAPTURE_BANNER) + return false; + + // If the capture target is no longer available (another bot already captured it), stop channeling + if (GameObject* targetFlag = currentSpell->m_targets.GetGOTarget()) + { + if (!targetFlag->isSpawned() || targetFlag->GetGoState() != GO_STATE_READY) + { + bot->InterruptNonMeleeSpells(true); + resetObjective(); + return false; + } + } + else + { + bot->InterruptNonMeleeSpells(true); + resetObjective(); + return false; + } + + if (bot->IsMounted()) + { + bot->RemoveAurasByType(SPELL_AURA_MOUNTED); + } + + if (bot->IsInDisallowedMountForm()) + { + bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + } + + if (bot->isMoving()) + { + bot->StopMoving(); + } + + return true; + }; + + // If we are already channeling the capture spell, keep the bot stationary and dismounted + if (keepStationaryWhileCapturing(CURRENT_CHANNELED_SPELL) || keepStationaryWhileCapturing(CURRENT_GENERIC_SPELL)) + return true; + // First identify which flag/base we're trying to interact with GameObject* targetFlag = nullptr; for (ObjectGuid const guid : closeObjects) { GameObject* go = botAI->GetGameObject(guid); if (!go) + { continue; + } + + bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID(); // Check if this object is a valid capture target - std::vector::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); - if (f == vFlagIds.end()) + std::vector::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); + if (f == vFlagIds.end() && !isEyCenterFlag) + { continue; + } // Verify the object is active and ready if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY) + { continue; + } // Check if we're in range (using double range for enemy detection) float const dist = bot->GetDistance(go); if (flagRange && dist > flagRange * 2.0f) + { continue; + } targetFlag = go; break; @@ -3655,7 +3723,7 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vector const& vPaths, std::vectorGetUnit(guid)) { - // Check if they're casting the capture spell - if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + // Check if they're casting or channeling the capture spell + Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!spell) { - if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER) - { - numCapturing++; - capturingPlayer = pFriend; - } + spell = pFriend->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + } + + if (spell && spell->m_spellInfo && spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER) + { + numCapturing++; + capturingPlayer = pFriend; } } } @@ -3704,9 +3775,11 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetGUID() == go->GetGUID(); + // Validate this is a capture target - std::vector::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); - if (f == vFlagIds.end()) + std::vector::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); + if (f == vFlagIds.end() && !isEyCenterFlag) continue; // Check object is active @@ -3722,12 +3795,40 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetEntry() == vFlagsWS[bot->GetTeamId()] - : bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0] - : false; + bool isWsBaseFlag = bgType == BATTLEGROUND_WS && go->GetEntry() == vFlagsWS[bot->GetTeamId()]; + bool isEyBaseFlag = bgType == BATTLEGROUND_EY && go->GetEntry() == vFlagsEY[0]; + + // Ensure bots are inside the Eye of the Storm capture circle before casting + if (bgType == BATTLEGROUND_EY) + { + GameObject* captureFlag = (isEyBaseFlag && eyCenterFlag) ? eyCenterFlag : go; + float const requiredRange = 2.5f; + if (!bot->IsWithinDistInMap(captureFlag, requiredRange)) + { + // Stay mounted while relocating to avoid mount/dismount loops + return MoveTo(bot->GetMapId(), captureFlag->GetPositionX(), captureFlag->GetPositionY(), + captureFlag->GetPositionZ()); + } + + // Once inside the circle, dismount and stop before starting the channel + if (bot->IsMounted()) + { + bot->RemoveAurasByType(SPELL_AURA_MOUNTED); + } + + if (bot->IsInDisallowedMountForm()) + { + bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + } + + if (bot->isMoving()) + { + bot->StopMoving(); + } + } // Don't capture own flag in WSG unless carrying enemy flag - if (atBase && bgType == BATTLEGROUND_WS && + if (isWsBaseFlag && bgType == BATTLEGROUND_WS && !(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG))) continue; @@ -3743,7 +3844,7 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetObjectSize() + go->GetObjectSize() + 0.1f; return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist), - go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ()); + go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ()); } // Dismount before capturing @@ -3772,7 +3873,7 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetTeamId() == TEAM_HORDE) { @@ -3811,28 +3912,50 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorIsMounted()) + { bot->RemoveAurasByType(SPELL_AURA_MOUNTED); + } if (bot->IsInDisallowedMountForm()) + { bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + } // Handle center flag differently (requires spell cast) - if (atBase) + if (isEyCenterFlag) { + for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; ++type) + { + if (Spell* currentSpell = bot->GetCurrentSpell(static_cast(type))) + { + // m_spellInfo may be null in some states: protect access + if (currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_CAPTURE_BANNER) + { + bot->StopMoving(); + botAI->SetNextCheckDelay(500); + return true; + } + } + } + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER); if (!spellInfo) return false; Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE); spell->m_targets.SetGOTarget(go); + + bot->StopMoving(); spell->prepare(&spell->m_targets); + botAI->WaitForSpellCast(spell); - //return true; Intended to make a bot cast SPELL_CAPTURE_BANNER and wait for spell finish, but doesn't work and causes infinite loop + resetObjective(); + return true; } // Pick up dropped flag @@ -3849,8 +3972,8 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ()); } } - default: - break; + default: + break; } }