diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index 3f6c83912..4e7868418 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -129,13 +129,24 @@ namespace Game.AI if (me.GetVictim() != null) return; - if (me.GetCreatureType() == CreatureType.NonCombatPet) - return; - if (me.HasReactState(ReactStates.Aggressive) && me.CanStartAttack(who, false)) AttackStart(who); } + void _OnOwnerCombatInteraction(Unit target) + { + if (target == null || !me.IsAlive()) + return; + + if (!me.HasReactState(ReactStates.Passive) && me.CanStartAttack(target, true)) + { + if (me.IsInCombat()) + me.AddThreat(target, 0.0f); + else + AttackStart(target); + } + } + // Distract creature, if player gets too close while stealthed/prowling public void TriggerAlert(Unit who) { @@ -433,8 +444,6 @@ namespace Game.AI // Called when spell hits a target public virtual void SpellHitTarget(Unit target, SpellInfo spell) {} - // Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc) - public virtual void AttackedBy(Unit attacker) { } public virtual bool IsEscorted() { return false; } // Called when creature is spawned or respawned @@ -455,10 +464,10 @@ namespace Game.AI public virtual void ReceiveEmote(Player player, TextEmotes emoteId) { } // Called when owner takes damage - public virtual void OwnerAttackedBy(Unit attacker) {} + public virtual void OwnerAttackedBy(Unit attacker) { _OnOwnerCombatInteraction(attacker); } // Called when owner attacks something - public virtual void OwnerAttacked(Unit target) {} + public virtual void OwnerAttacked(Unit target) { _OnOwnerCombatInteraction(target); } // called when the corpse of this creature gets removed public virtual void CorpseRemoved(long respawnDelay) {} diff --git a/Source/Game/AI/CoreAI/PetAI.cs b/Source/Game/AI/CoreAI/PetAI.cs index ee2356005..7ad3c01f2 100644 --- a/Source/Game/AI/CoreAI/PetAI.cs +++ b/Source/Game/AI/CoreAI/PetAI.cs @@ -115,7 +115,7 @@ namespace Game.AI // Aggressive - Allow auto select if owner or pet don't have a target // Stay - Only pick from pet or owner targets / attackers so targets won't run by // while chasing our owner. Don't do auto select. - // All other cases (ie: defensive) - Targets are assigned by AttackedBy(), OwnerAttackedBy(), OwnerAttacked(), etc. + // All other cases (ie: defensive) - Targets are assigned by DamageTaken(), OwnerAttackedBy(), OwnerAttacked(), etc. Unit nextTarget = SelectNextTarget(me.HasReactState(ReactStates.Aggressive)); if (nextTarget) @@ -307,16 +307,26 @@ namespace Game.AI HandleReturnMovement(); // Return } - public override void AttackStart(Unit victim) + public override void AttackStart(Unit target) { - // Overrides Unit.AttackStart to correctly evaluate Pet states + // Overrides Unit.AttackStart to prevent pet from switching off its assigned target + if (target == null || target == me) + return; + if (me.GetVictim() != null && me.GetVictim().IsAlive()) + return; + + _AttackStart(target); + } + + public void _AttackStart(Unit target) + { // Check all pet states to decide if we can attack this target - if (!CanAIAttack(victim)) + if (!CanAIAttack(target)) return; // Only chase if not commanded to stay or if stay but commanded to attack - DoAttack(victim, (!me.GetCharmInfo().HasCommandState(CommandStates.Stay) || me.GetCharmInfo().IsCommandAttack())); + DoAttack(target, (!me.GetCharmInfo().HasCommandState(CommandStates.Stay) || me.GetCharmInfo().IsCommandAttack())); } public override void OwnerAttackedBy(Unit attacker) @@ -324,7 +334,7 @@ namespace Game.AI // Called when owner takes damage. This function helps keep pets from running off // simply due to owner gaining aggro. - if (!attacker) + if (attacker == null || !me.IsAlive()) return; // Passive pets don't do anything @@ -345,7 +355,7 @@ namespace Game.AI // that they need to assist // Target might be null if called from spell with invalid cast targets - if (!target) + if (target == null || !me.IsAlive()) return; // Passive pets don't do anything @@ -620,23 +630,8 @@ namespace Game.AI } } - public override void AttackedBy(Unit attacker) + public override void DamageTaken(Unit attacker, ref uint damage) { - // Called when pet takes damage. This function helps keep pets from running off - // simply due to gaining aggro. - - if (!attacker) - return; - - // Passive pets don't do anything - if (me.HasReactState( ReactStates.Passive)) - return; - - // Prevent pet from disengaging from current target - if (me.GetVictim() && me.GetVictim().IsAlive()) - return; - - // Continue to evaluate and attack if necessary AttackStart(attacker); } diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index da044480d..358a2e01a 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -468,10 +468,13 @@ namespace Game.Entities // Spells such as auto-shot and others handled in WorldSession.HandleCastSpellOpcode if (IsTypeId(TypeId.Player)) { - Pet playerPet = ToPlayer().GetPet(); - - if (playerPet != null && playerPet.IsAlive()) - playerPet.GetAI().OwnerAttacked(victim); + foreach (Unit controlled in m_Controlled) + { + Creature cControlled = controlled.ToCreature(); + if (cControlled != null) + if (cControlled.IsAIEnabled) + cControlled.GetAI().OwnerAttacked(victim); + } } return true; } @@ -962,20 +965,19 @@ namespace Game.Entities // Signal to pets that their owner was attacked - except when DOT. if (damagetype != DamageEffectType.DOT) { - Pet pet = victim.ToPlayer().GetPet(); - - if (pet != null && pet.IsAlive()) - pet.GetAI().OwnerAttackedBy(this); + foreach (Unit controlled in victim.m_Controlled) + { + Creature cControlled = controlled.ToCreature(); + if (cControlled != null) + if (cControlled.IsAIEnabled) + cControlled.GetAI().OwnerAttackedBy(this); + } } if (victim.ToPlayer().GetCommandStatus(PlayerCommandStates.God)) return 0; } - // Signal the pet it was attacked so the AI can respond if needed - if (victim.IsTypeId(TypeId.Unit) && this != victim && victim.IsPet() && victim.IsAlive()) - victim.ToPet().GetAI().AttackedBy(this); - if (damagetype != DamageEffectType.NoDamage) { // interrupting auras with AURA_INTERRUPT_FLAG_DAMAGE before checking !damage (absorbed damage breaks that type of auras) @@ -1322,12 +1324,7 @@ namespace Game.Entities if (!target.IsInCombat() && !target.IsTypeId(TypeId.Player) && !target.ToCreature().HasReactState(ReactStates.Passive) && target.ToCreature().IsAIEnabled) - { - if (target.IsPet()) - target.ToCreature().GetAI().AttackedBy(this); // PetAI has special handler before AttackStart() - else target.ToCreature().GetAI().AttackStart(this); - } SetInCombatWith(target); target.SetInCombatWith(this); diff --git a/Source/Game/Handlers/PetHandler.cs b/Source/Game/Handlers/PetHandler.cs index 1d872a3cf..dde6fbf78 100644 --- a/Source/Game/Handlers/PetHandler.cs +++ b/Source/Game/Handlers/PetHandler.cs @@ -17,6 +17,7 @@ using Framework.Constants; using Framework.Database; +using Game.AI; using Game.Entities; using Game.Networking; using Game.Networking.Packets; @@ -199,7 +200,12 @@ namespace Game charmInfo.SetIsCommandFollow(false); charmInfo.SetIsReturning(false); - pet.ToCreature().GetAI().AttackStart(TargetUnit); + CreatureAI AI = pet.ToCreature().GetAI(); + PetAI petAI = (PetAI)AI; + if (petAI != null) + petAI._AttackStart(TargetUnit); // force target switch + else + AI.AttackStart(TargetUnit); //10% chance to play special pet attack talk, else growl if (pet.IsPet() && pet.ToPet().GetPetType() == PetType.Summon && pet != TargetUnit && RandomHelper.IRand(0, 100) < 10) @@ -367,7 +373,14 @@ namespace Game { pet.GetMotionMaster().Clear(); if (pet.ToCreature().IsAIEnabled) - pet.ToCreature().GetAI().AttackStart(unit_target); + { + CreatureAI AI = pet.ToCreature().GetAI(); + PetAI petAI = (PetAI)AI; + if (petAI != null) + petAI._AttackStart(unit_target); // force victim switch + else + AI.AttackStart(unit_target); + } } } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index b15d54943..84b9d4138 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -2678,10 +2678,17 @@ namespace Game.Spells // This prevents spells such as Hunter's Mark from triggering pet attack if (m_spellInfo.DmgClass != SpellDmgClass.None) { - Pet playerPet = playerCaster.GetPet(); - if (playerPet != null) - if (playerPet.IsAlive() && playerPet.IsControlled() && Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.Unit)) - playerPet.GetAI().OwnerAttacked(m_targets.GetUnitTarget()); + Unit unitTarget = m_targets.GetUnitTarget(); + if (unitTarget) + { + foreach (Unit controlled in playerCaster.m_Controlled) + { + Creature cControlled = controlled.ToCreature(); + if (cControlled != null) + if (cControlled.IsAIEnabled) + cControlled.GetAI().OwnerAttacked(unitTarget); + } + } } }