[CRASH FIX] Various crash fixes in regard to defensive checks around unit/targets and finer grained checks on tele ack (#1951)

Some basic defense checks on mainly unit/targets used in public
functions, and some minor tweaks with teleport ack.
https://github.com/mod-playerbots/mod-playerbots/issues/1934
https://github.com/mod-playerbots/mod-playerbots/issues/1957
This commit is contained in:
bashermens
2026-01-07 15:22:36 +01:00
committed by GitHub
parent c9cc4324d3
commit 3d9623f119
2 changed files with 149 additions and 60 deletions

View File

@@ -437,7 +437,7 @@ void PlayerbotAI::UpdateAIGroupMaster()
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal) void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
{ {
if (bot->IsBeingTeleported() || !bot->IsInWorld()) if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld())
return; return;
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I"; std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
@@ -516,23 +516,37 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
void PlayerbotAI::HandleCommands() void PlayerbotAI::HandleCommands()
{ {
ExternalEventHelper helper(aiObjectContext); ExternalEventHelper helper(aiObjectContext);
for (auto it = chatCommands.begin(); it != chatCommands.end();) for (auto it = chatCommands.begin(); it != chatCommands.end();)
{ {
time_t& checkTime = it->GetTime(); time_t& checkTime = it->GetTime();
if (checkTime && time(0) < checkTime) if (checkTime && time(nullptr) < checkTime)
{ {
++it; ++it;
continue; continue;
} }
const std::string& command = it->GetCommand();
Player* owner = it->GetOwner(); Player* owner = it->GetOwner();
if (!owner)
{
it = chatCommands.erase(it);
continue;
}
const std::string& command = it->GetCommand();
if (command.empty())
{
it = chatCommands.erase(it);
continue;
}
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER) if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
{ {
// ostringstream out; out << "Unknown command " << command; // ostringstream out; out << "Unknown command " << command;
// TellPlayer(out); // TellPlayer(out);
// helper.ParseChatCommand("help"); // helper.ParseChatCommand("help");
} }
it = chatCommands.erase(it); it = chatCommands.erase(it);
} }
} }
@@ -540,6 +554,9 @@ void PlayerbotAI::HandleCommands()
std::map<std::string, ChatMsg> chatMap; std::map<std::string, ChatMsg> chatMap;
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang) void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
{ {
if (!bot)
return;
std::string filtered = text; std::string filtered = text;
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE, if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
@@ -711,65 +728,82 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr
void PlayerbotAI::HandleTeleportAck() void PlayerbotAI::HandleTeleportAck()
{ {
if (!bot || !bot->GetSession())
return;
// only for bots
if (IsRealPlayer()) if (IsRealPlayer())
return; return;
// Clearing motion generators and stopping movement which prevents /*
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.) * FAR TELEPORT (worldport / map change)
bot->GetMotionMaster()->Clear(true); * Player may NOT be in world or grid here.
bot->StopMoving(); * Handle this FIRST.
*/
// Near teleport (within map/instance)
if (bot->IsBeingTeleportedNear())
{
// Previous versions manually added the bot to the map if it was not in the world.
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
// and clears IsBeingTeleportedNear().
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
// Send the near teleport ACK packet
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0);
p << uint32(0);
bot->GetSession()->HandleMoveTeleportAck(p);
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
SetNextCheckDelay(urand(1000, 2000));
}
// Far teleport (worldport / different map)
if (bot->IsBeingTeleportedFar()) if (bot->IsBeingTeleportedFar())
{ {
// Handle far teleport ACK:
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
bot->GetSession()->HandleMoveWorldportAck(); bot->GetSession()->HandleMoveWorldportAck();
// Ensure bot now has a valid map. If this fails, there is a core/session bug? // after worldport ACK the player should be in a valid map
if (!bot->GetMap()) if (!bot->GetMap())
{ {
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString()); LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
return;
} }
// Instance strategies after teleport // apply instance-related strategies after map attach
if (sPlayerbotAIConfig->applyInstanceStrategies) if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true); ApplyInstanceStrategies(bot->GetMapId(), true);
// healer DPS strategies if restrictions are enabled
if (sPlayerbotAIConfig->restrictHealerDPS) if (sPlayerbotAIConfig->restrictHealerDPS)
EvaluateHealerDpsStrategy(); EvaluateHealerDpsStrategy();
// Reset AI state to to before teleport conditions // reset AI state after teleport
Reset(true); Reset(true);
// Slightly longer delay to simulate far teleport latency (used cmangos delays) // clear movement only AFTER teleport is finalized and bot is in world
if (bot->IsInWorld() && bot->GetMotionMaster())
{
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
}
// simulate far teleport latency (cmangos-style)
SetNextCheckDelay(urand(2000, 5000)); SetNextCheckDelay(urand(2000, 5000));
return;
} }
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown); /*
* NEAR TELEPORT (same map / instance)
* Player MUST be in world (and in grid).
*/
if (bot->IsBeingTeleportedNear())
{
if (!bot->IsInWorld())
return;
Player* plMover = bot->m_mover ? bot->m_mover->ToPlayer() : nullptr;
if (!plMover)
return;
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0); // flags
p << uint32(0); // time
bot->GetSession()->HandleMoveTeleportAck(p);
// clear movement after successful relocation
if (bot->GetMotionMaster())
{
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
}
// simulate near teleport latency
SetNextCheckDelay(urand(1000, 2000));
return;
}
} }
void PlayerbotAI::Reset(bool full) void PlayerbotAI::Reset(bool full)
@@ -1286,7 +1320,12 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type); Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell) if (!spell)
continue; continue;
if (spell->GetSpellInfo()->Id == spellid)
SpellInfo const* spellInfo = spell->GetSpellInfo();
if (!spellInfo)
continue;
if (spellInfo->Id == spellid)
bot->InterruptSpell((CurrentSpellTypes)type); bot->InterruptSpell((CurrentSpellTypes)type);
} }
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get(); // LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
@@ -1728,6 +1767,7 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
} }
break; break;
} }
return true; return true;
} }
@@ -1821,10 +1861,9 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
bool PlayerbotAI::HasAggro(Unit* unit) bool PlayerbotAI::HasAggro(Unit* unit)
{ {
if (!unit) if (!IsValidUnit(unit))
{
return false; return false;
}
bool isMT = IsMainTank(bot); bool isMT = IsMainTank(bot);
Unit* victim = unit->GetVictim(); Unit* victim = unit->GetVictim();
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer())))) if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
@@ -2822,6 +2861,9 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit) bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
{ {
if (!unit || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
return false;
if (!aurEff) if (!aurEff)
return false; return false;
@@ -2829,6 +2871,8 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
return true; return true;
SpellInfo const* spellInfo = aurEff->GetSpellInfo(); SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
return false;
uint32 stacks = aurEff->GetBase()->GetStackAmount(); uint32 stacks = aurEff->GetBase()->GetStackAmount();
if (stacks >= spellInfo->StackAmount) if (stacks >= spellInfo->StackAmount)
@@ -2844,7 +2888,7 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount, bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
bool checkDuration) bool checkDuration)
{ {
if (!unit) if (!IsValidUnit(unit))
return false; return false;
std::wstring wnamepart; std::wstring wnamepart;
@@ -2940,7 +2984,7 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack) Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
{ {
if (!unit) if (!IsValidUnit(unit))
return nullptr; return nullptr;
std::wstring wnamepart; std::wstring wnamepart;
@@ -2958,6 +3002,9 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner
for (AuraEffect const* aurEff : auras) for (AuraEffect const* aurEff : auras)
{ {
SpellInfo const* spellInfo = aurEff->GetSpellInfo(); SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
continue;
std::string const& auraName = spellInfo->SpellName[0]; std::string const& auraName = spellInfo->SpellName[0];
// Directly skip if name mismatch (both length and content) // Directly skip if name mismatch (both length and content)
@@ -3038,6 +3085,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!target) if (!target)
target = bot; target = bot;
if (!IsValidUnit(target))
return false;
if (Pet* pet = bot->GetPet()) if (Pet* pet = bot->GetPet())
if (pet->HasSpell(spellid)) if (pet->HasSpell(spellid))
return true; return true;
@@ -3299,6 +3349,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget) bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget)
{ {
if (!IsValidUnit(target))
return false;
bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget); bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
if (result) if (result)
{ {
@@ -3311,15 +3364,19 @@ bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarg
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
{ {
if (!spellId) if (!spellId)
{
return false; return false;
}
if (!target) if (!target)
target = bot; target = bot;
Pet* pet = bot->GetPet(); if (!IsValidUnit(target))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
Pet* pet = bot->GetPet();
if (pet && pet->HasSpell(spellId)) if (pet && pet->HasSpell(spellId))
{ {
// List of spell IDs for which we do NOT want to toggle auto-cast or send message // List of spell IDs for which we do NOT want to toggle auto-cast or send message
@@ -3722,6 +3779,9 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId) if (!spellId)
return false; return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle(); Vehicle* vehicle = bot->GetVehicle();
if (!vehicle) if (!vehicle)
return false; return false;
@@ -3732,12 +3792,12 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
return false; return false;
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target; Unit* spellTarget = target;
if (!spellTarget) if (!spellTarget)
spellTarget = vehicleBase; spellTarget = vehicleBase;
if (!spellTarget) if (!IsValidUnit(spellTarget))
return false; return false;
if (vehicleBase->HasSpellCooldown(spellId)) if (vehicleBase->HasSpellCooldown(spellId))
@@ -3804,6 +3864,9 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId) if (!spellId)
return false; return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle(); Vehicle* vehicle = bot->GetVehicle();
if (!vehicle) if (!vehicle)
return false; return false;
@@ -3814,12 +3877,12 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
return false; return false;
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target; Unit* spellTarget = target;
if (!spellTarget) if (!spellTarget)
spellTarget = vehicleBase; spellTarget = vehicleBase;
if (!spellTarget) if (!IsValidUnit(spellTarget))
return false; return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
@@ -3972,9 +4035,13 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
void PlayerbotAI::WaitForSpellCast(Spell* spell) void PlayerbotAI::WaitForSpellCast(Spell* spell)
{ {
if (!spell)
return;
SpellInfo const* spellInfo = spell->GetSpellInfo(); SpellInfo const* spellInfo = spell->GetSpellInfo();
uint32 castTime = spell->GetCastTime(); uint32 castTime = spell->GetCastTime();
if (spellInfo->IsChanneled())
if (spellInfo && spellInfo->IsChanneled())
{ {
int32 duration = spellInfo->GetDuration(); int32 duration = spellInfo->GetDuration();
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration); bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
@@ -4022,6 +4089,9 @@ void PlayerbotAI::RemoveAura(std::string const name)
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell) bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
{ {
if (!IsValidUnit(target))
return false;
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get(); uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
if (!spellid || !target->IsNonMeleeSpellCast(true)) if (!spellid || !target->IsNonMeleeSpellCast(true))
return false; return false;
@@ -4050,17 +4120,25 @@ bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const sp
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType) bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
{ {
if (!target->IsInWorld()) if (!IsValidUnit(target) || !target->IsAlive())
{
return false; return false;
}
if (!IsValidPlayer(bot))
return false;
bool isFriend = bot->IsFriendlyTo(target); bool isFriend = bot->IsFriendlyTo(target);
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras(); Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
if (!visibleAuras)
return false;
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr) for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
{ {
Aura* aura = itr->second->GetBase(); if (!itr->second)
continue;
if (aura->IsPassive()) Aura* aura = itr->second->GetBase();
if (!aura || aura->IsPassive() || aura->IsRemoved())
continue; continue;
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() && if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
@@ -4068,6 +4146,8 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
continue; continue;
SpellInfo const* spellInfo = aura->GetSpellInfo(); SpellInfo const* spellInfo = aura->GetSpellInfo();
if (!spellInfo)
continue;
bool isPositiveSpell = spellInfo->IsPositive(); bool isPositiveSpell = spellInfo->IsPositive();
if (isPositiveSpell && isFriend) if (isPositiveSpell && isFriend)
@@ -4079,6 +4159,7 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
if (canDispel(spellInfo, dispelType)) if (canDispel(spellInfo, dispelType))
return true; return true;
} }
return false; return false;
} }
@@ -5709,7 +5790,7 @@ void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, Obje
// item on unit // item on unit
void PlayerbotAI::ImbueItem(Item* item, Unit* target) void PlayerbotAI::ImbueItem(Item* item, Unit* target)
{ {
if (!target) if (!IsValidUnit(target))
return; return;
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID()); ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());

View File

@@ -616,7 +616,15 @@ private:
void HandleCommands(); void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false; bool _isBotInitializing = false;
inline bool IsValidUnit(const Unit* unit) const
{
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();
}
inline bool IsValidPlayer(const Player* player) const
{
return player && player->GetSession() && player->IsInWorld() && !player->IsDuringRemoveFromWorld() &&
!player->IsBeingTeleported();
}
protected: protected:
Player* bot; Player* bot;
Player* master; Player* master;