Local snapshot for Docker build (includes mod-ale)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PetAI.h"
|
||||
#include "CharmInfo.h"
|
||||
#include "Creature.h"
|
||||
#include "Errors.h"
|
||||
#include "Group.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "Spell.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Util.h"
|
||||
|
||||
int32 PetAI::Permissible(Creature const* creature)
|
||||
{
|
||||
if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN))
|
||||
{
|
||||
if (reinterpret_cast<Guardian const*>(creature)->GetOwner()->IsPlayer())
|
||||
return PERMIT_BASE_PROACTIVE;
|
||||
return PERMIT_BASE_REACTIVE;
|
||||
}
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
PetAI::PetAI(Creature* c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK)
|
||||
{
|
||||
UpdateAllies();
|
||||
}
|
||||
|
||||
bool PetAI::_needToStop()
|
||||
{
|
||||
// This is needed for charmed creatures, as once their target was reset other effects can trigger threat
|
||||
if (me->IsCharmed() && me->GetVictim() == me->GetCharmer())
|
||||
return true;
|
||||
|
||||
// xinef: dont allow to follow targets out of visibility range
|
||||
if (me->GetExactDist(me->GetVictim()) > me->GetVisibilityRange() - 5.0f)
|
||||
return true;
|
||||
|
||||
// dont allow pets to follow targets far away from owner
|
||||
if (Unit* owner = me->GetCharmerOrOwner())
|
||||
if (owner->GetExactDist(me) >= (owner->GetVisibilityRange() - 10.0f))
|
||||
return true;
|
||||
|
||||
return !me->CanCreatureAttack(me->GetVictim());
|
||||
}
|
||||
|
||||
void PetAI::PetStopAttack()
|
||||
{
|
||||
_stopAttack();
|
||||
}
|
||||
|
||||
void PetAI::_stopAttack()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
LOG_DEBUG("entities.unit.ai", "Creature stoped attacking cuz his dead [{}]", me->GetGUID().ToString());
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop();
|
||||
me->GetThreatMgr().RemoveMeFromThreatLists();
|
||||
return;
|
||||
}
|
||||
|
||||
me->AttackStop();
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
me->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
ClearCharmInfoFlags();
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
void PetAI::_doMeleeAttack()
|
||||
{
|
||||
// Xinef: Imps cannot attack with melee
|
||||
if (!_canMeleeAttack())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
bool PetAI::_canMeleeAttack()
|
||||
{
|
||||
combatRange = 0.f;
|
||||
switch (me->GetEntry())
|
||||
{
|
||||
case ENTRY_IMP:
|
||||
case ENTRY_WATER_ELEMENTAL:
|
||||
case ENTRY_WATER_ELEMENTAL_PERM:
|
||||
{
|
||||
for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
|
||||
{
|
||||
uint32 spellID = me->GetPetAutoSpellOnPos(i);
|
||||
switch (spellID)
|
||||
{
|
||||
case IMP_FIREBOLT_RANK_1:
|
||||
case IMP_FIREBOLT_RANK_2:
|
||||
case IMP_FIREBOLT_RANK_3:
|
||||
case IMP_FIREBOLT_RANK_4:
|
||||
case IMP_FIREBOLT_RANK_5:
|
||||
case IMP_FIREBOLT_RANK_6:
|
||||
case IMP_FIREBOLT_RANK_7:
|
||||
case IMP_FIREBOLT_RANK_8:
|
||||
case IMP_FIREBOLT_RANK_9:
|
||||
case WATER_ELEMENTAL_WATERBOLT_1:
|
||||
case WATER_ELEMENTAL_WATERBOLT_2:
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
||||
int32 mana = me->GetPower(POWER_MANA);
|
||||
|
||||
if (mana >= spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()))
|
||||
{
|
||||
combatRange = spellInfo->GetMaxRange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PetAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive() || !me->GetCharmInfo())
|
||||
return;
|
||||
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
|
||||
if (m_updateAlliesTimer <= diff)
|
||||
// UpdateAllies self set update timer
|
||||
UpdateAllies();
|
||||
else
|
||||
m_updateAlliesTimer -= diff;
|
||||
|
||||
if (me->GetVictim() && me->GetVictim()->IsAlive())
|
||||
{
|
||||
// is only necessary to stop casting, the pet must not exit combat
|
||||
if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me))
|
||||
{
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_needToStop())
|
||||
{
|
||||
LOG_DEBUG("entities.unit.ai", "Pet AI stopped attacking [{}]", me->GetGUID().ToString());
|
||||
_stopAttack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check before attacking to prevent pets from leaving stay position
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
{
|
||||
if (me->GetCharmInfo()->IsCommandAttack() || (me->GetCharmInfo()->IsAtStay() && me->IsWithinMeleeRange(me->GetVictim())))
|
||||
_doMeleeAttack();
|
||||
}
|
||||
else
|
||||
_doMeleeAttack();
|
||||
}
|
||||
else if (!me->GetCharmInfo() || (!me->GetCharmInfo()->GetForcedSpell() && !(me->IsPet() && me->ToPet()->HasTempSpell()) && !me->HasUnitState(UNIT_STATE_CASTING)))
|
||||
{
|
||||
if (me->HasReactState(REACT_AGGRESSIVE) || me->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(me->HasReactState(REACT_AGGRESSIVE));
|
||||
|
||||
if (nextTarget)
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
// xinef: charm info must be always available
|
||||
if (!me->GetCharmInfo())
|
||||
return;
|
||||
|
||||
// Autocast (casted only in combat or persistent spells in any state)
|
||||
if (!me->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
if (owner && owner->IsPlayer() && me->GetCharmInfo()->GetForcedSpell() && me->GetCharmInfo()->GetForcedTarget())
|
||||
{
|
||||
owner->ToPlayer()->GetSession()->HandlePetActionHelper(me, me->GetGUID(), std::abs(me->GetCharmInfo()->GetForcedSpell()), ACT_ENABLED, me->GetCharmInfo()->GetForcedTarget());
|
||||
|
||||
// xinef: if spell was casted properly and we are in passive mode, handle return
|
||||
if (!me->GetCharmInfo()->GetForcedSpell() && me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->StopMoving();
|
||||
}
|
||||
else
|
||||
_stopAttack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// xinef: dont allow ghouls to cast spells below 75 energy
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul() && me->GetPower(POWER_ENERGY) < 75)
|
||||
return;
|
||||
|
||||
typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList;
|
||||
TargetSpellList targetSpellStore;
|
||||
|
||||
for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
|
||||
{
|
||||
uint32 spellID = me->GetPetAutoSpellOnPos(i);
|
||||
if (!spellID)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (me->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
|
||||
continue;
|
||||
|
||||
// check spell cooldown, this should be checked in CheckCast...
|
||||
if (me->HasSpellCooldown(spellInfo->Id))
|
||||
continue;
|
||||
|
||||
if (spellInfo->IsPositive())
|
||||
{
|
||||
if (spellInfo->CanBeUsedInCombat())
|
||||
{
|
||||
// Check if we're in combat or commanded to attack (exlude auras with infinity duration)
|
||||
if (!me->IsInCombat() && spellInfo->GetMaxDuration() != -1 && !me->IsPetInCombat())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
|
||||
spell->LoadScripts(); // xinef: load for CanAutoCast (calling CheckPetCast)
|
||||
bool spellUsed = false;
|
||||
|
||||
// Some spells can target enemy or friendly (DK Ghoul's Leap)
|
||||
// Check for enemy first (pet then owner)
|
||||
Unit* target = me->getAttackerForHelper();
|
||||
if (!target && owner)
|
||||
target = owner->getAttackerForHelper();
|
||||
|
||||
if (target)
|
||||
{
|
||||
if (CanAttack(target) && spell->CanAutoCast(target))
|
||||
{
|
||||
targetSpellStore.emplace_back(target, spell);
|
||||
spellUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (spellInfo->HasEffect(SPELL_EFFECT_JUMP_DEST))
|
||||
{
|
||||
if (!spellUsed)
|
||||
delete spell;
|
||||
|
||||
continue; // Pets must only jump to target
|
||||
}
|
||||
|
||||
// No enemy, check friendly
|
||||
if (!spellUsed)
|
||||
{
|
||||
for (ObjectGuid const& guid : m_AllySet)
|
||||
{
|
||||
Unit* ally = ObjectAccessor::GetUnit(*me, guid);
|
||||
|
||||
//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.emplace_back(ally, spell);
|
||||
spellUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No valid targets at all
|
||||
if (!spellUsed)
|
||||
delete spell;
|
||||
}
|
||||
else if (me->GetVictim() && CanAttack(me->GetVictim(), spellInfo) && spellInfo->CanBeUsedInCombat())
|
||||
{
|
||||
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE);
|
||||
if (spell->CanAutoCast(me->GetVictim()))
|
||||
targetSpellStore.emplace_back(me->GetVictim(), spell);
|
||||
else
|
||||
delete spell;
|
||||
}
|
||||
}
|
||||
|
||||
//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 (!me->HasInArc(M_PI, target))
|
||||
{
|
||||
me->SetInFront(target);
|
||||
if (target && target->IsPlayer())
|
||||
me->SendUpdateToPlayer(target->ToPlayer());
|
||||
|
||||
if (owner && owner->IsPlayer())
|
||||
me->SendUpdateToPlayer(owner->ToPlayer());
|
||||
}
|
||||
|
||||
me->AddSpellCooldown(spell->m_spellInfo->Id, 0, 0);
|
||||
|
||||
spell->prepare(&targets);
|
||||
}
|
||||
|
||||
// deleted cached Spell objects
|
||||
for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr)
|
||||
delete itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::UpdateAllies()
|
||||
{
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
Group* group = nullptr;
|
||||
|
||||
m_updateAlliesTimer = 10 * IN_MILLISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
|
||||
|
||||
if (!owner)
|
||||
return;
|
||||
else if (owner->IsPlayer())
|
||||
group = owner->ToPlayer()->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(me->GetGUID());
|
||||
if (group) //add group
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||
{
|
||||
Player* Target = itr->GetSource();
|
||||
if (!Target || !Target->IsInMap(owner) || !group->SameSubGroup(owner->ToPlayer(), Target))
|
||||
continue;
|
||||
|
||||
if (Target->GetGUID() == owner->GetGUID())
|
||||
continue;
|
||||
|
||||
m_AllySet.insert(Target->GetGUID());
|
||||
}
|
||||
}
|
||||
else //remove group
|
||||
m_AllySet.insert(owner->GetGUID());
|
||||
}
|
||||
|
||||
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 (me->GetVictim() && me->GetVictim() != victim)
|
||||
return;
|
||||
|
||||
// Xinef: if pet is channeling a spell and owner killed something different, dont interrupt it
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING) && me->GetGuidValue(UNIT_FIELD_CHANNEL_OBJECT) && me->GetGuidValue(UNIT_FIELD_CHANNEL_OBJECT) != victim->GetGUID())
|
||||
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
|
||||
me->AttackStop();
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
|
||||
// Before returning to owner, see if there are more things to attack
|
||||
if (Unit* nextTarget = SelectNextTarget(false))
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
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, (!me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->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;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_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);
|
||||
}
|
||||
|
||||
void PetAI::OwnerAttacked(Unit* target)
|
||||
{
|
||||
// Called when owner attacks something. Allows defensive pets to know
|
||||
// that they need to assist
|
||||
|
||||
// Target might be nullptr if called from spell with invalid cast targets
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me->GetVictim() && me->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
|
||||
|
||||
// Passive pets don't do next target selection
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return nullptr;
|
||||
|
||||
// Check pet attackers first so we don't drag a bunch of targets to the owner
|
||||
if (Unit* myAttacker = me->getAttackerForHelper())
|
||||
if (!myAttacker->HasBreakableByDamageCrowdControlAura() && me->CanCreatureAttack(myAttacker))
|
||||
return myAttacker;
|
||||
|
||||
// Check pet's attackers first to prevent dragging mobs back to owner
|
||||
if (me->HasTauntAura())
|
||||
{
|
||||
const Unit::AuraEffectList& tauntAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
||||
if (!tauntAuras.empty())
|
||||
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
|
||||
if (Unit* caster = (*itr)->GetCaster())
|
||||
if (me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, me->GetGUID()))
|
||||
return caster;
|
||||
}
|
||||
|
||||
// Not sure why we wouldn't have an owner but just in case...
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// Check owner attackers
|
||||
if (Unit* ownerAttacker = owner->getAttackerForHelper())
|
||||
if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->CanCreatureAttack(ownerAttacker))
|
||||
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 = owner->GetVictim())
|
||||
if (me->CanCreatureAttack(ownerVictim))
|
||||
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 (allowAutoSelect)
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsReturning() || me->GetCharmInfo()->IsFollowing() || me->GetCharmInfo()->IsAtStay())
|
||||
{
|
||||
if (Unit* nearTarget = me->ToCreature()->SelectNearestTargetInAttackDistance(MAX_AGGRO_RADIUS))
|
||||
{
|
||||
if (nearTarget->IsPlayer() && nearTarget->ToPlayer()->IsPvP() && !owner->IsPvP()) // If owner is not PvP flagged and target is PvP flagged, do not attack
|
||||
{
|
||||
return nullptr; /// @todo: try for another target
|
||||
}
|
||||
else
|
||||
{
|
||||
return nearTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default - no valid targets
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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 (me->isPossessed())
|
||||
return;
|
||||
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
if (me->GetCharmInfo()->HasStayPosition())
|
||||
{
|
||||
// Return to previous position where stay was clicked
|
||||
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
|
||||
{
|
||||
float x, y, z;
|
||||
me->GetCharmInfo()->GetStayPosition(x, y, z);
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsReturning(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MovePoint(me->GetGUID().GetCounter(), x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // COMMAND_FOLLOW
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsFollowing() && !me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsReturning(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
me->GetCharmInfo()->SetForcedSpell(0);
|
||||
me->GetCharmInfo()->SetForcedTargetGUID();
|
||||
|
||||
// xinef: remember that npcs summoned by npcs can also be pets
|
||||
me->GetThreatMgr().ClearAllThreat();
|
||||
me->ClearInPetCombat();
|
||||
}
|
||||
|
||||
void PetAI::SpellHit(Unit* caster, SpellInfo const* spellInfo)
|
||||
{
|
||||
// Xinef: taunt behavior code
|
||||
if (spellInfo->HasAura(SPELL_AURA_MOD_TAUNT) && !me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
me->GetCharmInfo()->SetForcedSpell(0);
|
||||
me->GetCharmInfo()->SetForcedTargetGUID();
|
||||
|
||||
if (CanAttack(caster, spellInfo))
|
||||
{
|
||||
// Only chase if not commanded to stay or if stay but commanded to attack
|
||||
DoAttack(caster, (!me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->IsCommandAttack()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::DoAttack(Unit* target, bool chase)
|
||||
{
|
||||
// Handles attack with or without chase and also resets flags
|
||||
// for next update / creature kill
|
||||
|
||||
if (me->Attack(target, true))
|
||||
{
|
||||
// xinef: properly fix fake combat after pet is sent to attack
|
||||
if (Unit* owner = me->GetOwner())
|
||||
owner->SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
|
||||
me->SetUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
|
||||
|
||||
// Play sound to let the player know the pet is attacking something it picked on its own
|
||||
if (me->HasReactState(REACT_AGGRESSIVE) && !me->GetCharmInfo()->IsCommandAttack())
|
||||
me->SendPetAIReaction(me->GetGUID());
|
||||
|
||||
if (chase)
|
||||
{
|
||||
bool oldCmdAttack = me->GetCharmInfo()->IsCommandAttack(); // This needs to be reset after other flags are cleared
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells
|
||||
|
||||
if (_canMeleeAttack())
|
||||
{
|
||||
std::optional<ChaseAngle> chaseAngle;
|
||||
if (combatRange == 0.f && !target->IsPlayer() && !target->IsPet())
|
||||
chaseAngle.emplace(float(M_PI), float(M_PI_4));
|
||||
|
||||
me->GetMotionMaster()->MoveChase(target, ChaseRange(0.f, combatRange), chaseAngle);
|
||||
}
|
||||
}
|
||||
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_ACTIVE, false);
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 == me->GetGUID().GetCounter() && me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->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 (me->GetCharmerOrOwner() && me->GetCharmInfo() && data == me->GetCharmerOrOwner()->GetGUID().GetCounter() && me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsFollowing(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool PetAI::CanAttack(Unit* target, SpellInfo const* spellInfo)
|
||||
{
|
||||
// Evaluates wether 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 (!target->IsAlive())
|
||||
{
|
||||
// xinef: if target is invalid, pet should evade automaticly
|
||||
// Clear target to prevent getting stuck on dead targets
|
||||
//me->AttackStop();
|
||||
//me->InterruptNonMeleeSpells(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// xinef: check unit states of pet
|
||||
if (me->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
// xinef: pets of mounted players have stunned flag only, check this also
|
||||
if (me->HasUnitFlag(UNIT_FLAG_STUNNED))
|
||||
return false;
|
||||
|
||||
// pussywizard: TEMP!
|
||||
if (!me->GetCharmInfo())
|
||||
{
|
||||
LOG_INFO("misc", "PetAI::CanAttack (A1) - {}, {}", me->GetEntry(), me->GetOwnerGUID().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Passive - passive pets can attack if told to
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// CC - mobs under crowd control can be attacked if owner commanded
|
||||
if (target->HasBreakableByDamageCrowdControlAura() && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS)))
|
||||
return me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// Returning - pets ignore attacks only if owner clicked follow
|
||||
if (me->GetCharmInfo()->IsReturning())
|
||||
return !me->GetCharmInfo()->IsCommandFollow();
|
||||
|
||||
// Stay - can attack if target is within range or commanded to
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
return (me->IsWithinMeleeRange(target) || me->GetCharmInfo()->IsCommandAttack());
|
||||
|
||||
// Pets attacking something (or chasing) should only switch targets if owner tells them to
|
||||
if (me->GetVictim() && me->GetVictim() != target)
|
||||
{
|
||||
// Forced change target if it's taunt
|
||||
if (spellInfo && spellInfo->HasAura(SPELL_AURA_MOD_TAUNT))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if our owner selected this target and clicked "attack"
|
||||
Unit* ownerTarget = nullptr;
|
||||
Unit* charmerOrOwner = me->GetCharmerOrOwner();
|
||||
|
||||
if (charmerOrOwner)
|
||||
{
|
||||
if (Player* owner = charmerOrOwner->ToPlayer())
|
||||
ownerTarget = owner->GetSelectedUnit();
|
||||
else
|
||||
ownerTarget = charmerOrOwner->GetVictim();
|
||||
}
|
||||
|
||||
if (ownerTarget && me->GetCharmInfo()->IsCommandAttack())
|
||||
return (target->GetGUID() == ownerTarget->GetGUID());
|
||||
}
|
||||
|
||||
// Follow
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
|
||||
return !me->GetCharmInfo()->IsReturning();
|
||||
|
||||
// default, though we shouldn't ever get here
|
||||
return false;
|
||||
}
|
||||
|
||||
void PetAI::ReceiveEmote(Player* player, uint32 emote)
|
||||
{
|
||||
if (me->GetOwnerGUID() && me->GetOwnerGUID() == player->GetGUID())
|
||||
switch (emote)
|
||||
{
|
||||
case TEXT_EMOTE_COWER:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(/*EMOTE_ONESHOT_ROAR*/EMOTE_ONESHOT_OMNICAST_GHOUL);
|
||||
break;
|
||||
case TEXT_EMOTE_ANGRY:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(/*EMOTE_ONESHOT_COWER*/EMOTE_STATE_STUN);
|
||||
break;
|
||||
case TEXT_EMOTE_GLARE:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(EMOTE_STATE_STUN);
|
||||
break;
|
||||
case TEXT_EMOTE_SOOTHE:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_OMNICAST_GHOUL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::ClearCharmInfoFlags()
|
||||
{
|
||||
// Quick access to set all flags to FALSE
|
||||
|
||||
CharmInfo* ci = me->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;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_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);
|
||||
}
|
||||
Reference in New Issue
Block a user