From 387c4912653c88d81ac19c909344de01c59da66e Mon Sep 17 00:00:00 2001 From: root Date: Fri, 3 Oct 2025 15:58:36 +1000 Subject: [PATCH 1/3] fix(Playerbots): Remove auras before teleporting to prevent crash Add RemoveAurasWithInterruptFlags call before all TeleportTo operations to prevent race condition crash in battlegrounds. The crash occurs when area auras (like "Entering Battleground") are queued for removal in Aura::UpdateTargetMap's targetsToRemove list, but the unit is deleted before the 500ms update cycle completes, causing SIGSEGV when accessing the dangling pointer. This fix removes auras with AURA_INTERRUPT_FLAG_TELEPORTED and AURA_INTERRUPT_FLAG_CHANGE_MAP before teleporting, matching the behavior in Player::TeleportTo for real players. Affected locations: - BattleGround join/teleport - Spirit healer/graveyard teleport - Corpse resurrection teleport - Meeting stone teleport - Master follow teleport - RPG unstuck teleport - Random bot teleport - Chat command teleport Raid-specific teleports excluded as they require separate testing. --- src/RandomPlayerbotMgr.cpp | 2 ++ src/strategy/actions/BattleGroundJoinAction.cpp | 1 + src/strategy/actions/BattleGroundTactics.cpp | 6 ++++++ src/strategy/actions/ChatShortcutActions.cpp | 1 + src/strategy/actions/MovementActions.cpp | 2 ++ src/strategy/actions/ReleaseSpiritAction.cpp | 2 ++ src/strategy/actions/ReviveFromCorpseAction.cpp | 2 ++ src/strategy/actions/UseMeetingStoneAction.cpp | 1 + src/strategy/rpg/NewRpgBaseAction.cpp | 1 + 9 files changed, 18 insertions(+) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 557450d0..61b1df49 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1853,6 +1853,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) botAI->Reset(true); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(loc.GetMapId(), x, y, z, 0); bot->SendMovementFlagUpdate(); @@ -3245,6 +3246,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } while (true); } + player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); player->TeleportTo(botPos); // player->Relocate(botPos.getX(), botPos.getY(), botPos.getZ(), botPos.getO()); diff --git a/src/strategy/actions/BattleGroundJoinAction.cpp b/src/strategy/actions/BattleGroundJoinAction.cpp index f4581624..542c7566 100644 --- a/src/strategy/actions/BattleGroundJoinAction.cpp +++ b/src/strategy/actions/BattleGroundJoinAction.cpp @@ -176,6 +176,7 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type) continue; memberBotAI->Reset(); + member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); LOG_INFO("playerbots", "Bot {} <{}>: Member of <{}>", member->GetGUID().ToString().c_str(), diff --git a/src/strategy/actions/BattleGroundTactics.cpp b/src/strategy/actions/BattleGroundTactics.cpp index 74bc236a..5e9e7daf 100644 --- a/src/strategy/actions/BattleGroundTactics.cpp +++ b/src/strategy/actions/BattleGroundTactics.cpp @@ -4289,9 +4289,15 @@ bool ArenaTactics::moveToCenter(Battleground* bg) { // they like to hang around at the tip of the pipes doing nothing, so we just teleport them down if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4) + { + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); + } if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4) + { + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); + } } break; case BATTLEGROUND_RV: diff --git a/src/strategy/actions/ChatShortcutActions.cpp b/src/strategy/actions/ChatShortcutActions.cpp index 0e99c5fe..40d66697 100644 --- a/src/strategy/actions/ChatShortcutActions.cpp +++ b/src/strategy/actions/ChatShortcutActions.cpp @@ -106,6 +106,7 @@ bool FollowChatShortcutAction::Execute(Event event) else botAI->TellMaster("You are too far away from me! I will there soon."); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), master->GetOrientation()); return true; } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 371d11c2..6758b916 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -1148,6 +1148,7 @@ bool MovementAction::Follow(Unit* target, float distance, float angle) if ((target->GetMap() && target->GetMap()->IsBattlegroundOrArena()) || (bot->GetMap() && bot->GetMap()->IsBattlegroundOrArena())) return false; + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(target->GetMapId(), x, y, z, bot->GetOrientation()); } else @@ -1175,6 +1176,7 @@ bool MovementAction::Follow(Unit* target, float distance, float angle) bot->CombatStop(true); botAI->TellMasterNoFacing("I will there soon."); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation()); return false; } diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index 92b7ac55..0d725f74 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -147,6 +147,7 @@ bool AutoReleaseSpiritAction::HandleBattlegroundSpiritHealer() // and in IOC it's not within clicking range when they res in own base // Teleport to nearest friendly Spirit Healer when not currently in range of one. + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); @@ -244,6 +245,7 @@ int64 RepopAction::CalculateDeadTime() const void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) const { + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index f2810747..83897dc2 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -169,6 +169,7 @@ bool FindCorpseAction::Execute(Event event) if (deadTime > delay) { bot->GetMotionMaster()->Clear(); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); } @@ -350,6 +351,7 @@ bool SpiritHealerAction::Execute(Event event) // if (!botAI->HasActivePlayerMaster()) // { context->GetValue("death count")->Set(dCount + 1); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); // } diff --git a/src/strategy/actions/UseMeetingStoneAction.cpp b/src/strategy/actions/UseMeetingStoneAction.cpp index 68eecce6..24d58aff 100644 --- a/src/strategy/actions/UseMeetingStoneAction.cpp +++ b/src/strategy/actions/UseMeetingStoneAction.cpp @@ -225,6 +225,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player) player->GetMotionMaster()->Clear(); AI_VALUE(LastMovement&, "last movement").clear(); + player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); player->TeleportTo(mapId, x, y, z, 0); if (botAI->HasStrategy("stay", botAI->GetState())) diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index 9af540dc..acd34078 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -67,6 +67,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) bot->GetName(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name); + bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP); return bot->TeleportTo(dest); } From d26c2a354912a6f23870137eeaade489de05c600 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 5 Oct 2025 16:15:09 +1100 Subject: [PATCH 2/3] fix: Clean visibility references before bot teleport to prevent crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PLAYERHOOK_ON_BEFORE_TELEPORT to proactively clean visibility references when a bot teleports between maps. This prevents a race condition where: 1. Bot A teleports and its visible objects start getting cleaned up 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map 3. Those objects may already be freed, causing a segmentation fault at GridNotifiers.cpp:65 in IsWorldObjectOutOfSightRange() The fix only affects bots to avoid changing behavior for real players. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/Playerbots.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index b5334ba7..e3059056 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -87,7 +87,8 @@ public: PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS, PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE, PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT, - PLAYERHOOK_ON_GIVE_EXP + PLAYERHOOK_ON_GIVE_EXP, + PLAYERHOOK_ON_BEFORE_TELEPORT }) {} void OnPlayerLogin(Player* player) override @@ -121,6 +122,26 @@ public: } } + bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override + { + // Only apply to bots to prevent affecting real players + if (!player || !player->GetSession()->IsBot()) + return true; + + // If changing maps, proactively clean visibility references to prevent + // stale pointers in other players' visibility maps during the teleport. + // This fixes a race condition where: + // 1. Bot A teleports and its visible objects start getting cleaned up + // 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map + // 3. Those objects may already be freed, causing a segmentation fault + if (player->GetMapId() != mapid && player->IsInWorld()) + { + player->GetObjectVisibilityContainer().CleanVisibilityReferences(); + } + + return true; // Allow teleport to continue + } + void OnPlayerAfterUpdate(Player* player, uint32 diff) override { if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) From c90b155a70f02dc8d30b4347dd4ad23dc45e8b8f Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Oct 2025 11:22:45 +1100 Subject: [PATCH 3/3] fix: Replace static m_botReleaseTimes with per-bot storage to prevent race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a thread safety issue where multiple bots dying in battlegrounds simultaneously would corrupt the shared static unordered_map, causing segmentation faults. Changes: - Remove: static m_botReleaseTimes map from AutoReleaseSpiritAction - Add: bgReleaseAttemptTime member to PlayerbotAI (per-bot storage) - Update: All references to use per-bot storage instead of static map Why this fixes the crash: - Each PlayerbotAI instance is accessed by only one map update thread - No cross-thread access to shared data structures - No mutex/locking required - thread-safe by design - Automatic cleanup when bot is destroyed Thread-safe solution: Per-bot state eliminates race conditions without performance overhead. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/PlayerbotAI.h | 1 + src/strategy/actions/ReleaseSpiritAction.cpp | 14 ++++++-------- src/strategy/actions/ReleaseSpiritAction.h | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 04deae3a..180606e8 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -601,6 +601,7 @@ public: NewRpgInfo rpgInfo; NewRpgStatistic rpgStatistic; std::unordered_set lowPriorityQuest; + time_t bgReleaseAttemptTime = 0; // Schedules a callback to run once after milliseconds. void AddTimedEvent(std::function callback, uint32 delayMs); diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index 0d725f74..666e42ab 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -192,12 +192,11 @@ bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const { // The below delays release to spirit with 6 seconds. // This prevents currently casted (ranged) spells to be re-directed to the died bot's ghost. - const int32_t botId = bot->GetGUID().GetRawValue(); - // If the bot already is a spirit, erase release time and return true + // If the bot already is a spirit, reset release time and return true if (bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) { - m_botReleaseTimes.erase(botId); + botAI->bgReleaseAttemptTime = 0; return true; } @@ -205,14 +204,13 @@ bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const const time_t now = time(nullptr); constexpr time_t RELEASE_DELAY = 6; - auto& lastReleaseTime = m_botReleaseTimes[botId]; - if (lastReleaseTime == 0) - lastReleaseTime = now; + if (botAI->bgReleaseAttemptTime == 0) + botAI->bgReleaseAttemptTime = now; - if (now - lastReleaseTime < RELEASE_DELAY) + if (now - botAI->bgReleaseAttemptTime < RELEASE_DELAY) return false; - m_botReleaseTimes.erase(botId); + botAI->bgReleaseAttemptTime = 0; return true; } diff --git a/src/strategy/actions/ReleaseSpiritAction.h b/src/strategy/actions/ReleaseSpiritAction.h index debeab84..57851214 100644 --- a/src/strategy/actions/ReleaseSpiritAction.h +++ b/src/strategy/actions/ReleaseSpiritAction.h @@ -38,7 +38,6 @@ private: bool ShouldAutoRelease() const; bool ShouldDelayBattlegroundRelease() const; - inline static std::unordered_map m_botReleaseTimes; time_t m_bgGossipTime = 0; };