diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 3d1ae51cb..b2535a446 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -239,14 +239,16 @@ void CreatureAI::EnterEvadeMode(EvadeReason why) { if (Unit* owner = me->GetCharmerOrOwner()) { + // Owned creatures (pets/guardians) follow their owner — clear evade state + // so they can re-enter combat immediately via CanBeginCombat + me->ClearUnitState(UNIT_STATE_EVADE); me->GetMotionMaster()->Clear(false); me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); } else { // Required to prevent attacking creatures that are evading and cause them to reenter combat - // Does not apply to MoveFollow - me->AddUnitState(UNIT_STATE_EVADE); + // Does not apply to MoveFollow — UNIT_STATE_EVADE is already set from _EnterEvadeMode me->GetMotionMaster()->MoveTargetedHome(); } } @@ -381,8 +383,11 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) return false; } - // Set evade state early to prevent recursion when CombatStop triggers JustExitedCombat - // This will be cleared in MoveTargetedHome for pets/guardians following their owner + // Set evade state early to prevent recursion: CombatStop below purges combat + // refs, which triggers JustExitedCombat -> EnterEvadeMode -> _EnterEvadeMode. + // The IsInEvadeMode() check above will catch it. + // EnterEvadeMode will clear this for owned creatures (pets/guardians) that + // use MoveFollow instead of MoveTargetedHome. me->AddUnitState(UNIT_STATE_EVADE); // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 391bb12e3..7d8cb92c0 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -7333,19 +7333,22 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) // set position before any AI calls/assistance //if (IsCreature()) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); - if (creature && !IsControlledByPlayer()) + if (creature) { EngageWithTarget(victim); - creature->SendAIReaction(AI_REACTION_HOSTILE); + if (!IsControlledByPlayer()) + { + creature->SendAIReaction(AI_REACTION_HOSTILE); - /// @todo: Implement aggro range, detection range and assistance range templates - if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE))) - creature->CallAssistance(); + /// @todo: Implement aggro range, detection range and assistance range templates + if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE))) + creature->CallAssistance(); - creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD)); + creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD)); - SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); + } } // delay offhand weapon attack by 50% of the base attack time diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index db6ef1361..e86aadd7a 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -306,6 +306,8 @@ struct npc_pet_dk_army_of_the_dead : public AggressorAI { npc_pet_dk_army_of_the_dead(Creature* creature) : AggressorAI(creature) { } + // Restrict MoveInLineOfSight aggro to targets already fighting our owner, + // so ghouls don't pull extra packs on their own. bool CanAIAttack(Unit const* target) const override { if (!target) @@ -315,6 +317,26 @@ struct npc_pet_dk_army_of_the_dead : public AggressorAI return false; return AggressorAI::CanAIAttack(target); } + + // Owner started attacking a target — engage immediately. + // We bypass OnOwnerCombatInteraction because CanStartAttack -> CanAIAttack + // may reject the target before combat refs are established. + void OwnerAttacked(Unit* target) override + { + if (!target || !me->IsAlive() || me->HasReactState(REACT_PASSIVE)) + return; + if (me->IsValidAttackTarget(target)) + AttackStart(target); + } + + // Owner was attacked — help defend. + void OwnerAttackedBy(Unit* attacker) override + { + if (!attacker || !me->IsAlive() || me->HasReactState(REACT_PASSIVE)) + return; + if (me->IsValidAttackTarget(attacker)) + AttackStart(attacker); + } }; struct npc_pet_dk_dancing_rune_weapon : public NullCreatureAI