Files
mod-playerbots/src/strategy/actions/ReviveFromCorpseAction.cpp
root 387c491265 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.
2025-10-03 15:58:36 +10:00

366 lines
12 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ReviveFromCorpseAction.h"
#include "Event.h"
#include "FleeManager.h"
#include "GameGraveyard.h"
#include "MapMgr.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "RandomPlayerbotMgr.h"
#include "ServerFacade.h"
#include "Corpse.h"
bool ReviveFromCorpseAction::Execute(Event event)
{
Player* master = botAI->GetGroupMaster();
Corpse* corpse = bot->GetCorpse();
// follow master when master revives
WorldPacket& p = event.getPacket();
if (!p.empty() && p.GetOpcode() == CMSG_RECLAIM_CORPSE && master && !corpse && bot->IsAlive())
{
if (sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
sPlayerbotAIConfig->farDistance))
{
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
{
botAI->TellMasterNoFacing("Welcome back!");
botAI->ChangeStrategy("+follow,-stay", BOT_STATE_NON_COMBAT);
return true;
}
}
}
if (!corpse)
return false;
// if (corpse->GetGhostTime() + bot->GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP) >
// time(nullptr))
// return false;
if (master)
{
if (!GET_PLAYERBOT_AI(master) && master->isDead() && master->GetCorpse() &&
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
sPlayerbotAIConfig->farDistance))
return false;
}
if (!botAI->HasRealPlayerMaster())
{
uint32 dCount = AI_VALUE(uint32, "death count");
if (dCount >= 5)
{
return botAI->DoSpecificAction("spirit healer");
}
}
LOG_DEBUG("playerbots", "Bot {} {}:{} <{}> revives at body", bot->GetGUID().ToString().c_str(),
bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str());
bot->GetMotionMaster()->Clear();
bot->StopMoving();
WorldPacket packet(CMSG_RECLAIM_CORPSE);
packet << bot->GetGUID();
bot->GetSession()->HandleReclaimCorpseOpcode(packet);
return true;
}
bool FindCorpseAction::Execute(Event event)
{
if (bot->InBattleground())
return false;
Player* master = botAI->GetGroupMaster();
Corpse* corpse = bot->GetCorpse();
if (!corpse)
return false;
// if (master)
// {
// if (!GET_PLAYERBOT_AI(master) &&
// sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "master target"),
// sPlayerbotAIConfig->farDistance)) return false;
// }
uint32 dCount = AI_VALUE(uint32, "death count");
if (!botAI->HasRealPlayerMaster())
{
if (dCount >= 5)
{
// LOG_INFO("playerbots", "Bot {} {}:{} <{}>: died too many times, was revived and teleported",
// bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(),
// bot->GetName().c_str());
context->GetValue<uint32>("death count")->Set(0);
// sRandomPlayerbotMgr->RandomTeleportForLevel(bot);
sRandomPlayerbotMgr->Revive(bot);
return true;
}
}
WorldPosition botPos(bot);
WorldPosition corpsePos(corpse);
WorldPosition moveToPos = corpsePos;
WorldPosition masterPos(master);
float reclaimDist = CORPSE_RECLAIM_RADIUS - 5.0f;
float corpseDist = botPos.distance(corpsePos);
int64 deadTime = time(nullptr) - corpse->GetGhostTime();
bool moveToMaster = master && master != bot && masterPos.fDist(corpsePos) < reclaimDist;
// Should we ressurect? If so, return false.
if (corpseDist < reclaimDist)
{
if (moveToMaster) // We are near master.
{
if (botPos.fDist(masterPos) < sPlayerbotAIConfig->spellDistance)
return false;
}
else if (deadTime > 8 * MINUTE) // We have walked too long already.
return false;
else
{
GuidVector units = AI_VALUE(GuidVector, "possible targets no los");
if (botPos.getUnitsAggro(units, bot) == 0) // There are no mobs near.
return false;
}
}
// If we are getting close move to a save ressurrection spot instead of just the corpse.
if (corpseDist < sPlayerbotAIConfig->reactDistance)
{
if (moveToMaster)
moveToPos = masterPos;
else
{
FleeManager manager(bot, reclaimDist, 0.0, urand(0, 1), moveToPos);
if (manager.isUseful())
{
float rx, ry, rz;
if (manager.CalculateDestination(&rx, &ry, &rz))
moveToPos = WorldPosition(moveToPos.getMapId(), rx, ry, rz, 0.0);
else if (!moveToPos.GetReachableRandomPointOnGround(bot, reclaimDist, urand(0, 1)))
moveToPos = corpsePos;
}
}
}
// Actual mobing part.
bool moved = false;
if (!botAI->AllowActivity(ALL_ACTIVITY))
{
uint32 delay = sServerFacade->GetDistance2d(bot, corpse) /
bot->GetSpeed(MOVE_RUN); // Time a bot would take to travel to it's corpse.
delay = std::min(delay, uint32(10 * MINUTE)); // Cap time to get to corpse at 10 minutes.
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);
}
moved = true;
}
else
{
if (bot->isMoving())
moved = true;
else
{
if (deadTime < 10 * MINUTE && dCount < 5) // Look for corpse up to 30 minutes.
{
moved =
MoveTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), false, false);
}
if (!moved)
{
moved = botAI->DoSpecificAction("spirit healer", Event(), true);
}
}
}
return moved;
}
bool FindCorpseAction::isUseful()
{
if (bot->InBattleground())
return false;
return bot->GetCorpse();
}
GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
{
GraveyardStruct const* ClosestGrave = nullptr;
GraveyardStruct const* NewGrave = nullptr;
ClosestGrave = sGraveyard->GetClosestGraveyard(bot, bot->GetTeamId());
if (!startZone && ClosestGrave)
return ClosestGrave;
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && botAI->GetGroupMaster() && botAI->GetGroupMaster() != bot)
{
Player* master = botAI->GetGroupMaster();
if (master && master != bot)
{
ClosestGrave = sGraveyard->GetClosestGraveyard(master, bot->GetTeamId());
if (ClosestGrave)
return ClosestGrave;
}
}
else if (startZone && AI_VALUE(uint8, "durability"))
{
TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target");
if (travelTarget->getPosition())
{
WorldPosition travelPos = *travelTarget->getPosition();
if (travelPos.GetMapId() != uint32(-1))
{
uint32 areaId = 0;
uint32 zoneId = 0;
sMapMgr->GetZoneAndAreaId(bot->GetPhaseMask(), zoneId, areaId, travelPos.getMapId(), travelPos.getX(),
travelPos.getY(), travelPos.getZ());
ClosestGrave = sGraveyard->GetClosestGraveyard(travelPos.getMapId(), travelPos.getX(), travelPos.getY(),
travelPos.getZ(), bot->GetTeamId(), areaId, zoneId,
bot->getClass() == CLASS_DEATH_KNIGHT);
if (ClosestGrave)
return ClosestGrave;
}
}
}
std::vector<uint32> races;
if (bot->GetTeamId() == TEAM_ALLIANCE)
races = {RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_NIGHTELF};
else
races = {RACE_ORC, RACE_TROLL, RACE_TAUREN, RACE_UNDEAD_PLAYER};
float graveDistance = -1;
WorldPosition botPos(bot);
for (auto race : races)
{
for (uint32 cls = 0; cls < MAX_CLASSES; cls++)
{
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(race, cls);
if (!info)
continue;
uint32 areaId = 0;
uint32 zoneId = 0;
sMapMgr->GetZoneAndAreaId(bot->GetPhaseMask(), zoneId, areaId, info->mapId, info->positionX,
info->positionY, info->positionZ);
NewGrave = sGraveyard->GetClosestGraveyard(info->mapId, info->positionX, info->positionY, info->positionZ,
bot->GetTeamId(), areaId, zoneId, cls == CLASS_DEATH_KNIGHT);
if (!NewGrave)
continue;
WorldPosition gravePos(NewGrave->Map, NewGrave->x, NewGrave->y, NewGrave->z);
float newDist = botPos.fDist(gravePos);
if (graveDistance < 0 || newDist < graveDistance)
{
ClosestGrave = NewGrave;
graveDistance = newDist;
}
}
}
return ClosestGrave;
}
bool SpiritHealerAction::Execute(Event event)
{
Corpse* corpse = bot->GetCorpse();
if (!corpse)
{
botAI->TellError("I am not a spirit");
return false;
}
uint32 dCount = AI_VALUE(uint32, "death count");
int64 deadTime = time(nullptr) - corpse->GetGhostTime();
GraveyardStruct const* ClosestGrave =
GetGrave(dCount > 10 || deadTime > 15 * MINUTE || AI_VALUE(uint8, "durability") < 10);
if (bot->GetDistance2d(ClosestGrave->x, ClosestGrave->y) < sPlayerbotAIConfig->sightDistance)
{
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPIRITHEALER))
{
LOG_DEBUG("playerbots", "Bot {} {}:{} <{}> revives at spirit healer", bot->GetGUID().ToString().c_str(),
bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName());
PlayerbotChatHandler ch(bot);
bot->ResurrectPlayer(0.5f);
bot->SpawnCorpseBones();
context->GetValue<Unit*>("current target")->Set(nullptr);
bot->SetTarget();
botAI->TellMaster("Hello");
if (dCount > 20)
context->GetValue<uint32>("death count")->Set(0);
return true;
}
}
}
if (!ClosestGrave)
{
return false;
}
bool moved = false;
if (bot->IsWithinLOS(ClosestGrave->x, ClosestGrave->y, ClosestGrave->z))
moved = MoveNear(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.0);
else
moved = MoveTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, false, false);
if (moved)
return true;
// if (!botAI->HasActivePlayerMaster())
// {
context->GetValue<uint32>("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);
// }
// LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(),
// bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), bot->GetName().c_str());
// botAI->TellError("Cannot find any spirit healer nearby");
return false;
}
bool SpiritHealerAction::isUseful() { return bot->HasPlayerFlag(PLAYER_FLAGS_GHOST); }