From 37b60cd0bce1380a976ea6c7389d6d6805fde8fa Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:18:14 -0500 Subject: [PATCH] fix(Core/Combat): safe StopAttackFaction and restore escort evade (#25516) Co-authored-by: blinkysc --- src/server/game/AI/CreatureAI.cpp | 9 +++------ .../game/AI/ScriptedAI/ScriptedEscortAI.cpp | 9 +++++++++ src/server/game/AI/ScriptedAI/ScriptedEscortAI.h | 2 ++ src/server/game/Entities/Unit/Unit.cpp | 16 +++++++++++----- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 665b915cb..345ee7ad7 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -309,12 +309,9 @@ void CreatureAI::EngagementOver() void CreatureAI::JustExitedCombat() { - EngagementOver(); - - // If creature is alive, in world, and not already evading, trigger evade to return home - // Check IsInWorld to avoid evade during server shutdown/cleanup - if (me->IsAlive() && me->IsInWorld() && !me->IsInEvadeMode()) - EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + // No-op: synchronous EnterEvadeMode cascades via MemberEvaded and frees + // refs held by upstream iterators (StopAttackFaction crash). EngagementOver + // here also resets scripted fights on brief combat gaps (Valithria). } /*void CreatureAI::AttackedBy(Unit* attacker) diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index ab9d233ca..3bd9f43b8 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -189,6 +189,15 @@ void npc_escortAI::ReturnToLastPoint() me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z, FORCED_MOVEMENT_RUN); } +void npc_escortAI::JustExitedCombat() +{ + // Evade synchronously so UpdateAI does not push a waypoint spline before + // SelectVictim's evade fallback fires; stacked motion intents twitch. + EngagementOver(); + if (me->IsAlive() && me->IsInWorld() && !me->IsInEvadeMode()) + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); +} + void npc_escortAI::EnterEvadeMode(EvadeReason /*why*/) { me->GetThreatMgr().ClearAllThreat(); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h index d56e57804..26f6e578f 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -70,6 +70,8 @@ public: void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override; + void JustExitedCombat() override; + void UpdateAI(uint32 diff) override; //the "internal" update, calls UpdateEscortAI() virtual void UpdateEscortAI(uint32 diff); //used when it's needed to add code in update (abilities, scripted events, etc) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 9f8da4ece..06dfe6734 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -16105,13 +16105,19 @@ void Unit::StopAttackFaction(uint32 faction_id) ++itr; } - // End combat and threat references with creatures in this faction - std::vector refsToEnd; + // Collect GUIDs, not pointers: EndCombat can cascade through AI callbacks + // (formation MemberEvaded -> CombatStop) and free other refs in the list. + std::vector guidsToEnd; for (auto const& pair : m_combatManager.GetPvECombatRefs()) if (pair.second->GetOther(this)->GetFactionTemplateEntry()->faction == faction_id) - refsToEnd.push_back(pair.second); - for (CombatReference* ref : refsToEnd) - ref->EndCombat(); + guidsToEnd.push_back(pair.first); + for (ObjectGuid const& guid : guidsToEnd) + { + auto const& refs = m_combatManager.GetPvECombatRefs(); + auto it = refs.find(guid); + if (it != refs.end()) + it->second->EndCombat(); + } for (ControlSet::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) (*itr)->StopAttackFaction(faction_id);