fix(Core/Scripts): Fix DK pets not correctly attacking (#25128)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Treeston <treeston.mmoc@gmail.com>
Co-authored-by: Malcrom <malcromdev@gmail.com>
Co-authored-by: Aqua Deus <95978183+aquadeus@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-03-20 07:14:58 -05:00
committed by GitHub
parent ebcaddb98d
commit 48fa4d2856
5 changed files with 62 additions and 64 deletions

View File

@@ -198,7 +198,7 @@ void CreatureAI::OnOwnerCombatInteraction(Unit* target)
if (!target || !me->IsAlive()) if (!target || !me->IsAlive())
return; return;
if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target)) if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true))
me->EngageWithTarget(target); me->EngageWithTarget(target);
} }

View File

@@ -1840,44 +1840,39 @@ bool Creature::IsAlwaysDetectableFor(WorldObject const* seer) const
return false; return false;
} }
bool Creature::CanStartAttack(Unit const* who) const bool Creature::CanStartAttack(Unit const* who, bool force) const
{ {
if (IsCivilian()) if (IsCivilian())
return false; return false;
// This set of checks is should be done only for creatures // This set of checks is should be done only for creatures
if ((IsImmuneToNPC() && !who->IsPlayer()) || // flag is valid only for non player characters if ((IsImmuneToNPC() && !who->IsPlayer()) ||
(IsImmuneToPC() && who->IsPlayer())) // immune to PC and target is a player, return false (IsImmuneToPC() && who->IsPlayer()))
{
return false; return false;
}
if (Unit* owner = who->GetOwner()) if (Unit* owner = who->GetOwner())
if (owner->IsPlayer() && IsImmuneToPC()) // immune to PC and target has player owner if (owner->IsPlayer() && IsImmuneToPC())
return false; return false;
// Do not attack non-combat pets // Do not attack non-combat pets
if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
return false; 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; return false;
if (!_IsTargetAcceptable(who)) if (!force)
return false; {
if (!_IsTargetAcceptable(who))
return false;
if (IsNeutralToAll() || !IsWithinDistInMap(who, GetAggroRange(who) + m_CombatDistance, true, false, false)) // pussywizard: +m_combatDistance for turrets and similar if (IsNeutralToAll() || !IsWithinDistInMap(who, GetAggroRange(who) + m_CombatDistance, true, false, false))
return false; return false;
}
if (!CanCreatureAttack(who)) if (!CanCreatureAttack(who))
return false; return false;
if (HasUnitState(UNIT_STATE_STUNNED))
return false;
if (!IsHostileTo(who))
return false;
return IsWithinLOSInMap(who); return IsWithinLOSInMap(who);
} }

View File

@@ -253,7 +253,7 @@ public:
CreatureSpellCooldowns m_CreatureSpellCooldowns; CreatureSpellCooldowns m_CreatureSpellCooldowns;
uint32 m_ProhibitSchoolTime[7]; 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 GetAggroRange(Unit const* target) const;
float GetAttackDistance(Unit const* player) const; float GetAttackDistance(Unit const* player) const;
[[nodiscard]] float GetDetectionRange() const { return m_detectionDistance; } [[nodiscard]] float GetDetectionRange() const { return m_detectionDistance; }

View File

@@ -943,13 +943,13 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage
// Hook for OnDamage Event // Hook for OnDamage Event
sScriptMgr->OnDamage(attacker, victim, damage); 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 for (Unit* controlled : victim->m_Controlled)
Pet* pet = victim->ToPlayer()->GetPet(); if (Creature* cControlled = controlled->ToCreature())
if (CreatureAI* controlledAI = cControlled->AI())
if (pet && pet->IsAlive()) controlledAI->OwnerAttackedBy(attacker);
pet->AI()->OwnerAttackedBy(attacker);
} }
//Dont deal damage to unit if .cheat god is enable. //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 {}.", 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()); 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()); // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
if (creature && !IsControlledByPlayer()) if (creature && !IsControlledByPlayer())
{ {
// should not let player enter combat by right clicking target - doesn't helps
EngageWithTarget(victim); EngageWithTarget(victim);
creature->SendAIReaction(AI_REACTION_HOSTILE); creature->SendAIReaction(AI_REACTION_HOSTILE);
@@ -7363,6 +7355,16 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
if (meleeAttack) if (meleeAttack)
SendMeleeAttackStart(victim); 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; return true;
} }

View File

@@ -250,13 +250,29 @@ struct npc_pet_dk_ghoul : public CombatAI
if (!summoner || !summoner->IsPlayer()) if (!summoner || !summoner->IsPlayer())
return; 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); if (Unit* target = ObjectAccessor::GetUnit(*me, _summonTargetGUID))
me->GetMotionMaster()->MoveChase(victim); {
if (target->IsAlive() && me->IsValidAttackTarget(target))
AttackStart(target);
}
_summonTargetGUID.Clear();
} }
CombatAI::UpdateAI(diff);
} }
void JustDied(Unit* /*who*/) override void JustDied(Unit* /*who*/) override
@@ -264,6 +280,9 @@ struct npc_pet_dk_ghoul : public CombatAI
if (me->IsGuardian() || me->IsSummon()) if (me->IsGuardian() || me->IsSummon())
me->ToTempSummon()->UnSummon(); me->ToTempSummon()->UnSummon();
} }
private:
ObjectGuid _summonTargetGUID;
}; };
struct npc_pet_dk_risen_ally : public PossessedAI 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(); if (!target)
((Minion*)me)->SetFollowAngle(rand_norm() * 2 * M_PI); return false;
} Unit* owner = me->GetOwner();
if (owner && !target->IsInCombatWith(owner))
void IsSummonedBy(WorldObject* summoner) override return false;
{ return AggressorAI::CanAIAttack(target);
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);
}
}
}
} }
}; };