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/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)) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index ab28c25d..6d06303c 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1771,6 +1771,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(); @@ -3090,6 +3091,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..666e42ab 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"); @@ -191,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; } @@ -204,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; } @@ -244,6 +243,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/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; }; 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); }