Files
moonwell-core-vanilla/src/game/AI/PetAI.cpp
T
ratkosrb 9998f2de90 Add separate handler for SPELL_EFFECT_SUMMON_POSSESSED.
This fixes the eye of killrog + mind control crash without hacks.
2019-01-02 18:27:18 +02:00

737 lines
26 KiB
C++

/*
* Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
* Copyright (C) 2009-2011 MaNGOSZero <https://github.com/mangos/zero>
* Copyright (C) 2011-2016 Nostalrius <https://nostalrius.org>
* Copyright (C) 2016-2017 Elysium Project <https://github.com/elysium-project>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "PetAI.h"
#include "Errors.h"
#include "Pet.h"
#include "Player.h"
#include "DBCStores.h"
#include "Spell.h"
#include "ObjectAccessor.h"
#include "SpellMgr.h"
#include "Creature.h"
#include "World.h"
#include "Util.h"
#include "Group.h"
#include "SpellAuras.h"
int PetAI::Permissible(const Creature *creature)
{
if (creature->IsPet())
return PERMIT_BASE_SPECIAL;
return PERMIT_BASE_NO;
}
PetAI::PetAI(Creature *c) : CreatureAI(c), inCombat(false)
{
UpdateAllies();
// Warlock imp has no melee attack
hasMelee = (c->GetEntry() != 416);
}
bool PetAI::_needToStop() const
{
// This is needed for charmed creatures, as once their target was reset other effects can trigger threat
if (m_creature->isCharmed() && m_creature->getVictim() == m_creature->GetCharmer())
return true;
// Stop attacking when player is mounted
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return true;
Unit* pOwner = m_creature->GetCharmerOrOwnerOrSelf();
// Prevent creature pets from chasing forever
if (pOwner->IsCreature() && !pOwner->isInCombat() && m_creature->IsOutOfThreatArea(m_creature->getVictim()))
return true;
return !m_creature->getVictim()->isTargetableForAttack(false, pOwner->IsPlayer());
}
void PetAI::_stopAttack()
{
if (!m_creature->isAlive())
{
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
m_creature->CombatStop();
m_creature->getHostileRefManager().deleteReferences();
return;
}
m_creature->AttackStop();
m_creature->InterruptNonMeleeSpells(false);
//m_creature->SendMeleeAttackStop(); // Should stop pet's attack button from flashing
m_creature->GetCharmInfo()->SetIsCommandAttack(false);
ClearCharmInfoFlags();
HandleReturnMovement();
}
void PetAI::UpdateAI(const uint32 diff)
{
if (!m_creature->isAlive() || !m_creature->GetCharmInfo())
return;
// part of it must run during eyes of the Beast to update melee hits
bool playerControlled = m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED);
Unit* owner = m_creature->GetCharmerOrOwner();
if (m_updateAlliesTimer <= diff)
// UpdateAllies self set update timer
UpdateAllies();
else
m_updateAlliesTimer -= diff;
// First checking if we have some taunt on us
Unit* tauntTarget = NULL;
const Unit::AuraList& tauntAuras = m_creature->GetAurasByType(SPELL_AURA_MOD_TAUNT);
if (!tauntAuras.empty() && !playerControlled)
{
Unit* caster = NULL;
// Auras are pushed_back, last caster will be on the end
Unit::AuraList::const_iterator aura = tauntAuras.end();
while (aura != tauntAuras.begin())
{
--aura;
caster = (*aura)->GetCaster();
if (caster && caster->isTargetableForAttack())
{
tauntTarget = caster;
break;
}
}
if (tauntTarget)
DoAttack(tauntTarget, true);
}
if (m_creature->getVictim() && m_creature->getVictim()->isAlive())
{
if (_needToStop())
{
_stopAttack();
return;
}
if (hasMelee)
{
// Check before attacking to prevent pets from leaving stay position
bool attacked = false;
if (m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY))
{
if (m_creature->GetCharmInfo()->IsCommandAttack() || (m_creature->GetCharmInfo()->IsAtStay() && m_creature->CanReachWithMeleeAttack(m_creature->getVictim())))
{
if (!m_creature->HasInArc(M_PI_F, m_creature->getVictim()))
m_creature->SetInFront(m_creature->getVictim());
attacked = DoMeleeAttackIfReady();
}
}
else
attacked = DoMeleeAttackIfReady();
if (attacked && owner)
if (Unit* v = m_creature->getVictim()) // Victim may have died between
owner->SetInCombatWith(v);
}
}
else if (!playerControlled)
{
if (m_creature->HasReactState(REACT_AGGRESSIVE) || m_creature->GetCharmInfo()->IsAtStay())
{
// Every update we need to check targets only in certain cases
// 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.
Unit* nextTarget = SelectNextTarget(m_creature->HasReactState(REACT_AGGRESSIVE));
if (nextTarget)
AttackStart(nextTarget);
else if (!m_creature->HasReactState(REACT_PASSIVE))
{
if (m_creature->isInCombat())
m_creature->CombatStop();
HandleReturnMovement();
}
}
else
HandleReturnMovement();
}
// End of possessed pet updates
if (playerControlled)
return;
// Autocast (casted only in combat or persistent spells in any state)
if (!m_creature->IsNonMeleeSpellCasted(false))
{
typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList;
TargetSpellList targetSpellStore;
for (uint8 i = 0; i < m_creature->GetPetAutoSpellSize(); ++i)
{
uint32 spellID = m_creature->GetPetAutoSpellOnPos(i);
if (!spellID)
continue;
SpellEntry const *spellInfo = sSpellMgr.GetSpellEntry(spellID);
if (!spellInfo)
continue;
if (m_creature->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
continue;
// check spell cooldown
if (m_creature->HasSpellCooldown(spellInfo->Id))
continue;
if (IsPositiveSpell(spellInfo->Id))
{
if (!IsNonCombatSpell(spellInfo)) // Can be used in combat.
{
/*
Spells handled here:
Dash (1850), Dive (23145), Furious Howl (24604), Tainted Blood (19478)
Blood Pact (6307), Fire Shield (11771), Sacrifice ...
Consume Shadows (17767)
*/
// Warlock Sacrifice: do not auto cast if not in combat
bool castOnlyInCombat = IsSpellHaveEffect(spellInfo, SPELL_EFFECT_INSTAKILL);
if (!castOnlyInCombat)
{
int32 duration = GetSpellDuration(spellInfo);
int32 cooldown = GetSpellRecoveryTime(spellInfo);
// Keep this spell for when we will be in combat.
if (cooldown >= 0 && duration >= 0 && cooldown > duration)
castOnlyInCombat = true;
}
// 19478 - Tainted Blood, rank 1 enUS
if (spellInfo->SpellIconID == 153)
castOnlyInCombat = true;
// 2947 - Fire Shield, rank 1 enUS
// When set to auto-cast, the Imp will cast this on any party members within 30 yds if they receive a melee attack.
if (spellInfo->IsFitToFamily<SPELLFAMILY_WARLOCK, CF_WARLOCK_IMP_BUFFS>() && spellInfo->SpellVisual == 289)
castOnlyInCombat = false;
// Furious Howl: in combat only
if (IsSpellHaveAura(spellInfo, SPELL_AURA_MOD_DAMAGE_DONE))
castOnlyInCombat = true;
if (castOnlyInCombat && !m_creature->getVictim())
continue;
}
Spell *spell = new Spell(m_creature, spellInfo, false);
bool spellUsed = false;
// Some spells can target enemy or friendly (DK Ghoul's Leap)
// Check for enemy first (pet then owner)
Unit* target = m_creature->getAttackerForHelper();
if (!target && owner)
target = owner->getAttackerForHelper();
if (target)
{
if (CanAttack(target) && spell->CanAutoCast(target))
{
targetSpellStore.push_back(std::make_pair(target, spell));
spellUsed = true;
}
}
// No enemy, check friendly
if (!spellUsed)
{
for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
{
Unit* ally = m_creature->GetMap()->GetUnit(*tar);
//only buff targets that are in combat, unless the spell can only be cast while out of combat
if (!ally)
continue;
if (spell->CanAutoCast(ally))
{
targetSpellStore.push_back(std::make_pair(ally, spell));
spellUsed = true;
break;
}
}
}
// No valid targets at all
if (!spellUsed)
spell->Delete();
}
else if (m_creature->getVictim() && CanAttack(m_creature->getVictim()) && !IsNonCombatSpell(spellInfo))
{
Spell *spell = new Spell(m_creature, spellInfo, false);
if (spell->CanAutoCast(m_creature->getVictim()))
targetSpellStore.push_back(std::make_pair(m_creature->getVictim(), spell));
else
spell->Delete();
}
}
//found units to cast on to
if (!targetSpellStore.empty())
{
uint32 index = urand(0, targetSpellStore.size() - 1);
Spell* spell = targetSpellStore[index].second;
Unit* target = targetSpellStore[index].first;
targetSpellStore.erase(targetSpellStore.begin() + index);
SpellCastTargets targets;
targets.setUnitTarget(target);
if (!m_creature->HasInArc(M_PI_F, target))
{
m_creature->SetInFront(target);
if (target->GetTypeId() == TYPEID_PLAYER)
m_creature->SendCreateUpdateToPlayer((Player*)target);
if (owner && owner->GetTypeId() == TYPEID_PLAYER)
m_creature->SendCreateUpdateToPlayer((Player*)owner);
}
if (((Creature*)m_creature)->IsPet())
((Pet*)m_creature)->CheckLearning(spell->m_spellInfo->Id);
// 10% chance to play special pet attack talk, else growl
// actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
if (((Creature*)m_creature)->IsPet() && (((Pet*)m_creature)->getPetType() == SUMMON_PET) && (m_creature != target) && (urand(0, 100) < 10))
m_creature->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL);
else
m_creature->SendPetAIReaction();
spell->prepare(std::move(targets));
}
// deleted cached Spell objects
for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr)
itr->second->Delete();
}
// Update speed as needed to prevent dropping too far behind and despawning
m_creature->UpdateSpeed(MOVE_RUN, true);
m_creature->UpdateSpeed(MOVE_WALK, true);
}
void PetAI::UpdateAllies()
{
Unit* owner = m_creature->GetCharmerOrOwner();
Group *group = NULL;
m_updateAlliesTimer = 10 * IN_MILLISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
if (!owner)
return;
else if (owner->GetTypeId() == TYPEID_PLAYER)
group = ((Player*)owner)->GetGroup();
//only pet and owner/not in group->ok
if (m_AllySet.size() == 2 && !group)
return;
//owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
if (group && !group->isRaidGroup() && m_AllySet.size() == (group->GetMembersCount() + 2))
return;
m_AllySet.clear();
m_AllySet.insert(m_creature->GetObjectGuid());
if (group) //add group
{
for (GroupReference *itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
{
Player* target = itr->getSource();
if (!target || !group->SameSubGroup((Player*)owner, target))
continue;
if (target->GetObjectGuid() == owner->GetObjectGuid())
continue;
m_AllySet.insert(target->GetObjectGuid());
}
}
else //remove group
m_AllySet.insert(owner->GetObjectGuid());
}
void PetAI::KilledUnit(Unit* victim)
{
// Called from Unit::Kill() in case where pet or owner kills something
// if owner killed this victim, pet may still be attacking something else
if (m_creature->getVictim() && m_creature->getVictim() != victim)
return;
// Clear target just in case. May help problem where health / focus / mana
// regen gets stuck. Also resets attack command.
// Can't use _stopAttack() because that activates movement handlers and ignores
// next target selection
m_creature->AttackStop();
m_creature->InterruptNonMeleeSpells(false);
m_creature->SendMeleeAttackStop(victim); // Stops the pet's 'Attack' button from flashing
// Before returning to owner, see if there are more things to attack
if (Unit* nextTarget = SelectNextTarget(false))
AttackStart(nextTarget);
else
{
if (m_creature->isInCombat())
m_creature->CombatStop();
HandleReturnMovement(); // Return
}
}
void PetAI::AttackStart(Unit* target)
{
// Overrides Unit::AttackStart to correctly evaluate Pet states
// Check all pet states to decide if we can attack this target
if (!CanAttack(target))
return;
// Only chase if not commanded to stay or if stay but commanded to attack
DoAttack(target, (!m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY) || m_creature->GetCharmInfo()->IsCommandAttack()));
}
void PetAI::OwnerAttackedBy(Unit* attacker)
{
// Called when owner takes damage. This function helps keep pets from running off
// simply due to owner gaining aggro.
if (!attacker)
return;
// If owner attacked by fire, attacker=owner. Don't attack our owner !
if (!m_creature->IsValidAttackTarget(attacker))
return;
// Pet desactive (monture)
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return;
// Passive pets don't do anything
if (m_creature->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (m_creature->getVictim() && m_creature->getVictim()->isAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(attacker);
}
void PetAI::OwnerAttacked(Unit* target)
{
// Called when owner attacks something. Allows defensive pets to know
// that they need to assist
// Target might be NULL if called from spell with invalid cast targets
if (!target)
return;
if (!m_creature->IsValidAttackTarget(target))
return;
// Pet desactive (monture)
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return;
// Passive pets don't do anything
if (m_creature->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (m_creature->getVictim() && m_creature->getVictim()->isAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(target);
}
Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const
{
// Provides next target selection after current target death.
// This function should only be called internally by the AI
// Targets are not evaluated here for being valid targets, that is done in _CanAttack()
// The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations
// Pet desactive (monture)
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return NULL;
// Passive pets don't do next target selection
if (m_creature->HasReactState(REACT_PASSIVE))
return NULL;
// Not sure why we wouldn't have an owner but just in case...
Unit* owner = m_creature->GetCharmerOrOwner();
if (!owner)
return NULL;
// Check owner attackers
if (Unit* ownerAttacker = owner->getAttackerForHelper())
if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && owner->isInCombat())
return ownerAttacker;
// Check owner victim
// 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does
//if (Unit* ownerVictim = m_creature->GetCharmerOrOwner()->getVictim())
// return ownerVictim;
// Neither pet or owner had a target and aggressive pets can pick any target
// To prevent aggressive pets from chain selecting targets and running off, we
// only select a random target if certain conditions are met.
if (m_creature->HasReactState(REACT_AGGRESSIVE) && allowAutoSelect)
{
if (!m_creature->GetCharmInfo()->IsReturning() || m_creature->GetCharmInfo()->IsFollowing() || m_creature->GetCharmInfo()->IsAtStay())
if (Unit* nearTarget = m_creature->ToCreature()->SelectNearestHostileUnitInAggroRange(true))
return nearTarget;
}
// Default - no valid targets
return NULL;
}
void PetAI::HandleReturnMovement()
{
// Handles moving the pet back to stay or owner
// Prevent activating movement when under control of spells
// such as "Eyes of the Beast"
if (m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED))
return;
if (m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY))
{
if (!m_creature->GetCharmInfo()->IsAtStay() && !m_creature->GetCharmInfo()->IsReturning())
{
// Return to previous position where stay was clicked
float x, y, z;
m_creature->GetCharmInfo()->GetStayPosition(x, y, z);
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsReturning(true);
m_creature->GetMotionMaster()->Clear(false);
m_creature->GetMotionMaster()->MovePoint(m_creature->GetGUIDLow(), x, y, z);
}
}
else // COMMAND_FOLLOW
{
if (!m_creature->GetCharmInfo()->IsFollowing() && !m_creature->GetCharmInfo()->IsReturning())
{
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsReturning(true);
m_creature->GetMotionMaster()->Clear(false);
m_creature->GetMotionMaster()->MoveFollow(m_creature->GetCharmerOrOwner(), PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
}
}
}
void PetAI::DoAttack(Unit* target, bool chase)
{
// Handles attack with or without chase and also resets flags
// for next update / creature kill
if (m_creature->Attack(target, hasMelee))
{
// Play sound to let the player know the pet is attacking something it picked on its own
if (m_creature->HasReactState(REACT_AGGRESSIVE) && !m_creature->GetCharmInfo()->IsCommandAttack())
m_creature->SendPetAIReaction();
if (chase)
{
bool oldCmdAttack = m_creature->GetCharmInfo()->IsCommandAttack(); // This needs to be reset after other flags are cleared
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells
m_creature->GetMotionMaster()->Clear();
if (!hasMelee)
m_creature->SetCasterChaseDistance(25.0f);
m_creature->GetMotionMaster()->MoveChase(target);
}
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
{
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsAtStay(true);
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
}
// Flag owner for PvP if owner is player and target is flagged
Unit* pOwner = m_creature->GetCharmerOrOwner();
if (pOwner && pOwner->IsPlayer() && !pOwner->IsPvP())
{
pOwner->TogglePlayerPvPFlagOnAttackVictim(target);
}
}
}
void PetAI::MovementInform(uint32 moveType, uint32 data)
{
// Receives notification when pet reaches stay or follow owner
switch (moveType)
{
case POINT_MOTION_TYPE:
{
// Pet is returning to where stay was clicked. data should be
// pet's GUIDLow since we set that as the waypoint ID
if (data == m_creature->GetGUIDLow() && m_creature->GetCharmInfo()->IsReturning())
{
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsAtStay(true);
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
}
break;
}
case FOLLOW_MOTION_TYPE:
{
// If data is owner's GUIDLow then we've reached follow point,
// otherwise we're probably chasing a creature
if (m_creature->GetCharmerOrOwner() && m_creature->GetCharmInfo() && data == m_creature->GetCharmerOrOwner()->GetGUIDLow() && m_creature->GetCharmInfo()->IsReturning())
{
ClearCharmInfoFlags();
m_creature->GetCharmInfo()->SetIsFollowing(true);
}
break;
}
default:
break;
}
}
bool PetAI::CanAttack(Unit* target)
{
// Evaluates whether a pet can attack a specific target based on CommandState, ReactState and other flags
// IMPORTANT: The order in which things are checked is important, be careful if you add or remove checks
// Hmmm...
if (!target)
return false;
if (!m_creature->IsValidAttackTarget(target))
return false;
if (!target->isAlive())
{
// Clear target to prevent getting stuck on dead targets
m_creature->AttackStop();
m_creature->InterruptNonMeleeSpells(false);
m_creature->SendMeleeAttackStop(target);
return false;
}
// Pet desactive (monture)
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return false;
// Passive - passive pets can attack if told to
if (m_creature->HasReactState(REACT_PASSIVE))
return m_creature->GetCharmInfo()->IsCommandAttack();
// CC - mobs under crowd control can be attacked if owner commanded
if (target->HasBreakableByDamageCrowdControlAura())
return m_creature->GetCharmInfo()->IsCommandAttack();
// Returning - pets ignore attacks only if owner clicked follow
if (m_creature->GetCharmInfo()->IsReturning())
return !m_creature->GetCharmInfo()->IsCommandFollow();
// Stay - can attack if target is within range or commanded to
if (m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY))
return (m_creature->IsWithinMeleeRange(target) || m_creature->GetCharmInfo()->IsCommandAttack());
// Pets attacking something (or chasing) should only switch targets if owner tells them to
if (m_creature->getVictim() && m_creature->getVictim() != target)
{
// Check if our owner selected this target and clicked "attack"
Unit* owner = m_creature->GetCharmerOrOwner();
Unit* ownerTarget = NULL;
if (owner)
{
if (Player* playerOwner = owner->ToPlayer())
ownerTarget = playerOwner->GetSelectedUnit();
else
ownerTarget = owner->getVictim();
}
if (ownerTarget && m_creature->GetCharmInfo()->IsCommandAttack())
return (target->GetGUID() == ownerTarget->GetGUID());
}
// Follow
if (m_creature->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
return !m_creature->GetCharmInfo()->IsReturning();
// default, though we shouldn't ever get here
return false;
}
void PetAI::ClearCharmInfoFlags()
{
// Quick access to set all flags to FALSE
CharmInfo* ci = m_creature->GetCharmInfo();
if (ci)
{
ci->SetIsAtStay(false);
ci->SetIsCommandAttack(false);
ci->SetIsCommandFollow(false);
ci->SetIsFollowing(false);
ci->SetIsReturning(false);
}
}
void PetAI::AttackedBy(Unit* attacker)
{
// Called when pet takes damage. This function helps keep pets from running off
// simply due to gaining aggro.
if (!attacker)
return;
if (!m_creature->IsValidAttackTarget(attacker))
return;
// Pet desactive (monture)
if (m_creature->IsPet() && !((Pet*)m_creature)->IsEnabled())
return;
// Passive pets don't do anything
if (m_creature->HasReactState(REACT_PASSIVE))
return;
// Prevent pet from disengaging from current target
if (m_creature->getVictim() && m_creature->getVictim()->isAlive())
return;
// Continue to evaluate and attack if necessary
AttackStart(attacker);
}