2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "DBCStores.h"
25 #include "ObjectAccessor.h"
31 int PetAI::Permissible(const Creature
*creature
)
33 if( creature
->isPet())
34 return PERMIT_BASE_SPECIAL
;
36 return PERMIT_BASE_NO
;
39 PetAI::PetAI(Creature
*c
) : CreatureAI(c
), i_tracker(TIME_INTERVAL_LOOK
), inCombat(false)
45 void PetAI::MoveInLineOfSight(Unit
*u
)
47 if( !m_creature
->getVictim() && m_creature
->GetCharmInfo() &&
48 m_creature
->GetCharmInfo()->HasReactState(REACT_AGGRESSIVE
) &&
49 u
->isTargetableForAttack() && m_creature
->IsHostileTo( u
) &&
50 u
->isInAccessablePlaceFor(m_creature
))
52 float attackRadius
= m_creature
->GetAttackDistance(u
);
53 if(m_creature
->IsWithinDistInMap(u
, attackRadius
) && m_creature
->GetDistanceZ(u
) <= CREATURE_Z_ATTACK_RANGE
)
55 if(m_creature
->IsWithinLOSInMap(u
))
58 u
->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH
);
64 void PetAI::AttackStart(Unit
*u
)
66 if(!u
|| (m_creature
->isPet() && ((Pet
*)m_creature
)->getPetType() == MINI_PET
))
69 if(m_creature
->Attack(u
,true))
71 m_creature
->clearUnitState(UNIT_STAT_FOLLOW
);
72 // TMGs call CreatureRelocation which via MoveInLineOfSight can call this function
73 // thus with the following clear the original TMG gets invalidated and crash, doh
74 // hope it doesn't start to leak memory without this :-/
76 m_creature
->GetMotionMaster()->MoveChase(u
);
81 void PetAI::EnterEvadeMode()
85 bool PetAI::IsVisible(Unit
*pl
) const
87 return _isVisible(pl
);
90 bool PetAI::_needToStop() const
92 // This is needed for charmed creatures, as once their target was reset other effects can trigger threat
93 if(m_creature
->isCharmed() && m_creature
->getVictim() == m_creature
->GetCharmer())
96 return !m_creature
->getVictim()->isTargetableForAttack();
99 void PetAI::_stopAttack()
102 if( !m_creature
->isAlive() )
104 DEBUG_LOG("Creature stoped attacking cuz his dead [guid=%u]", m_creature
->GetGUIDLow());
105 m_creature
->StopMoving();
106 m_creature
->GetMotionMaster()->Clear();
107 m_creature
->GetMotionMaster()->MoveIdle();
108 m_creature
->CombatStop();
109 m_creature
->getHostilRefManager().deleteReferences();
114 Unit
* owner
= m_creature
->GetCharmerOrOwner();
116 if(owner
&& m_creature
->GetCharmInfo() && m_creature
->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW
))
118 m_creature
->GetMotionMaster()->MoveFollow(owner
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
122 m_creature
->clearUnitState(UNIT_STAT_FOLLOW
);
123 m_creature
->GetMotionMaster()->Clear();
124 m_creature
->GetMotionMaster()->MoveIdle();
126 m_creature
->AttackStop();
129 void PetAI::UpdateAI(const uint32 diff
)
131 if (!m_creature
->isAlive())
134 Unit
* owner
= m_creature
->GetCharmerOrOwner();
136 if(m_updateAlliesTimer
<= diff
)
137 // UpdateAllies self set update timer
140 m_updateAlliesTimer
-= diff
;
142 if (inCombat
&& !m_creature
->getVictim())
145 // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc.
146 if (m_creature
->getVictim())
150 DEBUG_LOG("Pet AI stoped attacking [guid=%u]", m_creature
->GetGUIDLow());
154 else if (m_creature
->IsStopped() || m_creature
->IsWithinDistInMap(m_creature
->getVictim(), ATTACK_DISTANCE
))
156 // required to be stopped cases
157 if (m_creature
->IsStopped() && m_creature
->IsNonMeleeSpellCasted(false))
159 if (m_creature
->hasUnitState(UNIT_STAT_FOLLOW
))
160 m_creature
->InterruptNonMeleeSpells(false);
164 // not required to be stopped case
165 else if (m_creature
->isAttackReady() && m_creature
->canReachWithAttack(m_creature
->getVictim()))
167 m_creature
->AttackerStateUpdate(m_creature
->getVictim());
169 m_creature
->resetAttackTimer();
171 if (!m_creature
->getVictim())
174 //if pet misses its target, it will also be the first in threat list
175 m_creature
->getVictim()->AddThreat(m_creature
,0.0f
);
182 else if (owner
&& m_creature
->GetCharmInfo())
184 if (owner
->isInCombat() && !(m_creature
->GetCharmInfo()->HasReactState(REACT_PASSIVE
) || m_creature
->GetCharmInfo()->HasCommandState(COMMAND_STAY
)))
186 AttackStart(owner
->getAttackerForHelper());
188 else if(m_creature
->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW
))
190 if (!m_creature
->hasUnitState(UNIT_STAT_FOLLOW
) )
192 m_creature
->GetMotionMaster()->MoveFollow(owner
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
197 // Autocast (casted only in combat or persistent spells in any state)
198 if (m_creature
->GetGlobalCooldown() == 0 && !m_creature
->IsNonMeleeSpellCasted(false))
200 typedef std::vector
<std::pair
<Unit
*, Spell
*> > TargetSpellList
;
201 TargetSpellList targetSpellStore
;
203 for (uint8 i
= 0; i
< m_creature
->GetPetAutoSpellSize(); ++i
)
205 uint32 spellID
= m_creature
->GetPetAutoSpellOnPos(i
);
209 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellID
);
213 // ignore some combinations of combat state and combat/noncombat spells
216 // ignore attacking spells, and allow only self/around spells
217 if (!IsPositiveSpell(spellInfo
->Id
))
220 // non combat spells allowed
221 // only pet spells have IsNonCombatSpell and not fit this reqs:
222 // Consume Shadows, Lesser Invisibility, so ignore checks for its
223 if (!IsNonCombatSpell(spellInfo
))
225 // allow only spell without spell cost or with spell cost but not duration limit
226 int32 duration
= GetSpellDuration(spellInfo
);
227 if ((spellInfo
->manaCost
|| spellInfo
->ManaCostPercentage
|| spellInfo
->manaPerSecond
) && duration
> 0)
230 // allow only spell without cooldown > duration
231 int32 cooldown
= GetSpellRecoveryTime(spellInfo
);
232 if (cooldown
>= 0 && duration
>= 0 && cooldown
> duration
)
238 // just ignore non-combat spells
239 if (IsNonCombatSpell(spellInfo
))
243 Spell
*spell
= new Spell(m_creature
, spellInfo
, false, 0);
245 if (inCombat
&& !m_creature
->hasUnitState(UNIT_STAT_FOLLOW
) && spell
->CanAutoCast(m_creature
->getVictim()))
247 targetSpellStore
.push_back(std::make_pair
<Unit
*, Spell
*>(m_creature
->getVictim(), spell
));
252 bool spellUsed
= false;
253 for(std::set
<uint64
>::const_iterator tar
= m_AllySet
.begin(); tar
!= m_AllySet
.end(); ++tar
)
255 Unit
* Target
= ObjectAccessor::GetUnit(*m_creature
,*tar
);
257 //only buff targets that are in combat, unless the spell can only be cast while out of combat
261 if(spell
->CanAutoCast(Target
))
263 targetSpellStore
.push_back(std::make_pair
<Unit
*, Spell
*>(Target
, spell
));
273 //found units to cast on to
274 if (!targetSpellStore
.empty())
276 uint32 index
= urand(0, targetSpellStore
.size() - 1);
278 Spell
* spell
= targetSpellStore
[index
].second
;
279 Unit
* target
= targetSpellStore
[index
].first
;
281 targetSpellStore
.erase(targetSpellStore
.begin() + index
);
283 SpellCastTargets targets
;
284 targets
.setUnitTarget( target
);
286 if (!m_creature
->HasInArc(M_PI
, target
))
288 m_creature
->SetInFront(target
);
289 if (target
->GetTypeId() == TYPEID_PLAYER
)
290 m_creature
->SendUpdateToPlayer((Player
*)target
);
292 if (owner
&& owner
->GetTypeId() == TYPEID_PLAYER
)
293 m_creature
->SendUpdateToPlayer( (Player
*)owner
);
296 m_creature
->AddCreatureSpellCooldown(spell
->m_spellInfo
->Id
);
298 spell
->prepare(&targets
);
301 // deleted cached Spell objects
302 for(TargetSpellList::const_iterator itr
= targetSpellStore
.begin(); itr
!= targetSpellStore
.end(); ++itr
)
307 bool PetAI::_isVisible(Unit
*u
) const
309 return m_creature
->IsWithinDist(u
,sWorld
.getConfig(CONFIG_SIGHT_GUARDER
))
310 && u
->isVisibleForOrDetect(m_creature
,m_creature
,true);
313 void PetAI::UpdateAllies()
315 Unit
* owner
= m_creature
->GetCharmerOrOwner();
316 Group
*pGroup
= NULL
;
318 m_updateAlliesTimer
= 10*IN_MILISECONDS
; //update friendly targets every 10 seconds, lesser checks increase performance
322 else if(owner
->GetTypeId() == TYPEID_PLAYER
)
323 pGroup
= ((Player
*)owner
)->GetGroup();
325 //only pet and owner/not in group->ok
326 if(m_AllySet
.size() == 2 && !pGroup
)
328 //owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
329 if(pGroup
&& !pGroup
->isRaidGroup() && m_AllySet
.size() == (pGroup
->GetMembersCount() + 2))
333 m_AllySet
.insert(m_creature
->GetGUID());
334 if(pGroup
) //add group
336 for(GroupReference
*itr
= pGroup
->GetFirstMember(); itr
!= NULL
; itr
= itr
->next())
338 Player
* Target
= itr
->getSource();
339 if(!Target
|| !pGroup
->SameSubGroup((Player
*)owner
, Target
))
342 if(Target
->GetGUID() == owner
->GetGUID())
345 m_AllySet
.insert(Target
->GetGUID());
349 m_AllySet
.insert(owner
->GetGUID());
352 void PetAI::AttackedBy(Unit
*attacker
)
354 //when attacked, fight back in case 1)no victim already AND 2)not set to passive AND 3)not set to stay, unless can it can reach attacker with melee attack anyway
355 if(!m_creature
->getVictim() && m_creature
->GetCharmInfo() && !m_creature
->GetCharmInfo()->HasReactState(REACT_PASSIVE
) &&
356 (!m_creature
->GetCharmInfo()->HasCommandState(COMMAND_STAY
) || m_creature
->canReachWithAttack(attacker
)))
357 AttackStart(attacker
);