Core - Fix Bots can pickup the flag in Eye of the Strom instantly, without cast time from the spawn point (#1704)

# 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.
This commit is contained in:
Alex Dcnh
2025-11-15 18:19:16 +01:00
committed by GitHub
parent cadbcbd447
commit 6effabfa42

View File

@@ -4,10 +4,12 @@
*/
#include "BattleGroundTactics.h"
#include "BattleGroundJoinAction.h"
#include <algorithm>
#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"
@@ -3578,6 +3581,16 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
GuidVector closePlayers;
float flagRange = 0.0f;
// Eye of the Storm helpers used later when handling capture positioning
BattlegroundEY* eyeBg = nullptr;
GameObject* eyCenterFlag = nullptr;
if (bgType == BATTLEGROUND_EY)
{
eyeBg = static_cast<BattlegroundEY*>(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<BattleBotPath*> const& vPaths, std::vector<ui
if (closeObjects.empty())
return false;
auto keepStationaryWhileCapturing = [&](CurrentSpellTypes spellType)
{
Spell* currentSpell = bot->GetCurrentSpell(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<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
if (f == vFlagIds.end())
std::vector<uint32>::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<BattleBotPath*> const& vPaths, std::vector<ui
}
// Check if friendly players are already capturing
if (!closePlayers.empty())
if (!closePlayers.empty() && bgType != BATTLEGROUND_EY)
{
// Track number of friendly players capturing and the closest one
uint32 numCapturing = 0;
@@ -3664,17 +3732,20 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
{
if (Unit* pFriend = botAI->GetUnit(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)
spell = pFriend->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
}
if (spell && spell->m_spellInfo && spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
{
numCapturing++;
capturingPlayer = pFriend;
}
}
}
}
// If friendlies are capturing, stay to defend but don't capture
if (numCapturing > 0 && capturingPlayer && bot->GetGUID() != capturingPlayer->GetGUID())
@@ -3704,9 +3775,11 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
if (!go)
continue;
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
// Validate this is a capture target
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
if (f == vFlagIds.end())
std::vector<uint32>::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<BattleBotPath*> const& vPaths, std::vector<ui
continue;
// Special handling for WSG and EY base flags
bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == 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;
@@ -3772,7 +3873,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
if (dist < INTERACTION_DISTANCE)
{
// Handle flag capture at base
if (atBase)
if (isWsBaseFlag)
{
if (bot->GetTeamId() == TEAM_HORDE)
{
@@ -3811,28 +3912,50 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
}
}
case BATTLEGROUND_EY:
{ // Issue: Currently bots in EY take flag instantly without casttime
{ // Handle Netherstorm flag capture requiring a channel
if (dist < INTERACTION_DISTANCE)
{
// Dismount before interacting
if (bot->IsMounted())
{
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<CurrentSpellTypes>(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