From 48fa4d2856fb8ddbce1948a0584bbb6b9b252aee Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:14:58 -0500 Subject: [PATCH] fix(Core/Scripts): Fix DK pets not correctly attacking (#25128) Co-authored-by: blinkysc Co-authored-by: Treeston Co-authored-by: Malcrom Co-authored-by: Aqua Deus <95978183+aquadeus@users.noreply.github.com> --- src/server/game/AI/CreatureAI.cpp | 2 +- .../game/Entities/Creature/Creature.cpp | 29 ++++----- src/server/game/Entities/Creature/Creature.h | 2 +- src/server/game/Entities/Unit/Unit.cpp | 30 ++++----- src/server/scripts/Pet/pet_dk.cpp | 63 ++++++++++--------- 5 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index e9c0ee353..fbe681109 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -198,7 +198,7 @@ void CreatureAI::OnOwnerCombatInteraction(Unit* target) if (!target || !me->IsAlive()) return; - if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target)) + if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true)) me->EngageWithTarget(target); } diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 0ea731156..815644e68 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1840,44 +1840,39 @@ bool Creature::IsAlwaysDetectableFor(WorldObject const* seer) const return false; } -bool Creature::CanStartAttack(Unit const* who) const +bool Creature::CanStartAttack(Unit const* who, bool force) const { if (IsCivilian()) return false; // This set of checks is should be done only for creatures - if ((IsImmuneToNPC() && !who->IsPlayer()) || // flag is valid only for non player characters - (IsImmuneToPC() && who->IsPlayer())) // immune to PC and target is a player, return false - { + if ((IsImmuneToNPC() && !who->IsPlayer()) || + (IsImmuneToPC() && who->IsPlayer())) return false; - } if (Unit* owner = who->GetOwner()) - if (owner->IsPlayer() && IsImmuneToPC()) // immune to PC and target has player owner + if (owner->IsPlayer() && IsImmuneToPC()) return false; // Do not attack non-combat pets if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) return false; - if (!CanFly() && (GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE + m_CombatDistance)) // too much Z difference, skip very costy vmap calculations here + if (!CanFly() && (GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE + m_CombatDistance)) return false; - if (!_IsTargetAcceptable(who)) - return false; + if (!force) + { + if (!_IsTargetAcceptable(who)) + return false; - if (IsNeutralToAll() || !IsWithinDistInMap(who, GetAggroRange(who) + m_CombatDistance, true, false, false)) // pussywizard: +m_combatDistance for turrets and similar - return false; + if (IsNeutralToAll() || !IsWithinDistInMap(who, GetAggroRange(who) + m_CombatDistance, true, false, false)) + return false; + } if (!CanCreatureAttack(who)) return false; - if (HasUnitState(UNIT_STATE_STUNNED)) - return false; - - if (!IsHostileTo(who)) - return false; - return IsWithinLOSInMap(who); } diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 2b33a764a..0669d6e45 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -253,7 +253,7 @@ public: CreatureSpellCooldowns m_CreatureSpellCooldowns; uint32 m_ProhibitSchoolTime[7]; - bool CanStartAttack(Unit const* u) const; + bool CanStartAttack(Unit const* u, bool force = false) const; float GetAggroRange(Unit const* target) const; float GetAttackDistance(Unit const* player) const; [[nodiscard]] float GetDetectionRange() const { return m_detectionDistance; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 754025045..565b350cd 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -943,13 +943,13 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage // Hook for OnDamage Event sScriptMgr->OnDamage(attacker, victim, damage); - if (victim->IsPlayer() && attacker != victim) + // Signal to pets that their owner was attacked - except when DOT. + if (attacker != victim && damagetype != DOT) { - // Signal to pets that their owner was attacked - Pet* pet = victim->ToPlayer()->GetPet(); - - if (pet && pet->IsAlive()) - pet->AI()->OwnerAttackedBy(attacker); + for (Unit* controlled : victim->m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttackedBy(attacker); } //Dont deal damage to unit if .cheat god is enable. @@ -2766,13 +2766,6 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A LOG_DEBUG("entities.unit", "AttackerStateUpdate: (NPC) {} attacked {} for {} dmg, absorbed {}, blocked {}, resisted {}.", GetGUID().ToString(), victim->GetGUID().ToString(), dmgInfo.GetDamage(), dmgInfo.GetAbsorb(), dmgInfo.GetBlock(), dmgInfo.GetResist()); - // Let the pet know we've started attacking someting. Handles melee attacks only - // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode - if (IsPlayer() && !m_Controlled.empty()) - for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) - if (Unit* pet = *itr) - if (pet->IsAlive() && pet->IsCreature()) - pet->ToCreature()->AI()->OwnerAttacked(victim); } } @@ -7342,7 +7335,6 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); if (creature && !IsControlledByPlayer()) { - // should not let player enter combat by right clicking target - doesn't helps EngageWithTarget(victim); creature->SendAIReaction(AI_REACTION_HOSTILE); @@ -7363,6 +7355,16 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) if (meleeAttack) SendMeleeAttackStart(victim); + // Let the pet know we've started attacking someting. Handles melee attacks only + // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode + if (IsPlayer()) + { + for (Unit* controlled : m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttacked(victim); + } + return true; } diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index e4b25ebf3..db6ef1361 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -250,13 +250,29 @@ struct npc_pet_dk_ghoul : public CombatAI if (!summoner || !summoner->IsPlayer()) return; - Player* player = summoner->ToPlayer(); + // Remember the owner's target so we can attack it after the rising stun expires. + if (Unit* victim = summoner->ToPlayer()->GetVictim()) + _summonTargetGUID = victim->GetGUID(); + } - if (Unit* victim = player->GetVictim()) + void UpdateAI(uint32 diff) override + { + // While stunned (rising animation), don't run CombatAI - just wait. + if (me->HasUnitState(UNIT_STATE_STUNNED)) + return; + + // Once the stun expires, attack the saved target from summon time. + if (!_summonTargetGUID.IsEmpty()) { - me->Attack(victim, true); - me->GetMotionMaster()->MoveChase(victim); + if (Unit* target = ObjectAccessor::GetUnit(*me, _summonTargetGUID)) + { + if (target->IsAlive() && me->IsValidAttackTarget(target)) + AttackStart(target); + } + _summonTargetGUID.Clear(); } + + CombatAI::UpdateAI(diff); } void JustDied(Unit* /*who*/) override @@ -264,6 +280,9 @@ struct npc_pet_dk_ghoul : public CombatAI if (me->IsGuardian() || me->IsSummon()) me->ToTempSummon()->UnSummon(); } + +private: + ObjectGuid _summonTargetGUID; }; struct npc_pet_dk_risen_ally : public PossessedAI @@ -283,36 +302,18 @@ struct npc_pet_dk_risen_ally : public PossessedAI } }; -struct npc_pet_dk_army_of_the_dead : public CombatAI +struct npc_pet_dk_army_of_the_dead : public AggressorAI { - npc_pet_dk_army_of_the_dead(Creature* creature) : CombatAI(creature) { } + npc_pet_dk_army_of_the_dead(Creature* creature) : AggressorAI(creature) { } - void InitializeAI() override + bool CanAIAttack(Unit const* target) const override { - CombatAI::InitializeAI(); - ((Minion*)me)->SetFollowAngle(rand_norm() * 2 * M_PI); - } - - void IsSummonedBy(WorldObject* summoner) override - { - if (Unit* owner = summoner->ToUnit()) - { - Unit* victim = owner->GetVictim(); - - if (victim && me->IsValidAttackTarget(victim)) - { - AttackStart(victim); - } - else - { - // If there is no valid target, attack the nearest enemy within 30m - if (Unit* nearest = me->SelectNearbyTarget(nullptr, 30.0f)) - { - if (me->IsValidAttackTarget(nearest)) - AttackStart(nearest); - } - } - } + if (!target) + return false; + Unit* owner = me->GetOwner(); + if (owner && !target->IsInCombatWith(owner)) + return false; + return AggressorAI::CanAIAttack(target); } };