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 "Database/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
) : i_pet(c
), i_tracker(TIME_INTERVAL_LOOK
), inCombat(false)
45 void PetAI::MoveInLineOfSight(Unit
*u
)
47 if( !i_pet
.getVictim() && i_pet
.GetCharmInfo() &&
48 i_pet
.GetCharmInfo()->HasReactState(REACT_AGGRESSIVE
) &&
49 u
->isTargetableForAttack() && i_pet
.IsHostileTo( u
) &&
50 u
->isInAccessablePlaceFor(&i_pet
))
52 float attackRadius
= i_pet
.GetAttackDistance(u
);
53 if(i_pet
.IsWithinDistInMap(u
, attackRadius
) && i_pet
.GetDistanceZ(u
) <= CREATURE_Z_ATTACK_RANGE
)
55 if(i_pet
.IsWithinLOSInMap(u
))
58 u
->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH
);
64 void PetAI::AttackStart(Unit
*u
)
66 if( inCombat
|| !u
|| (i_pet
.isPet() && ((Pet
&)i_pet
).getPetType() == MINI_PET
) )
69 if(i_pet
.Attack(u
,true))
71 i_pet
.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 i_pet
.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(i_pet
.isCharmed() && i_pet
.getVictim() == i_pet
.GetCharmer())
96 return !i_pet
.getVictim()->isTargetableForAttack();
99 void PetAI::_stopAttack()
102 if( !i_pet
.isAlive() )
104 DEBUG_LOG("Creature stoped attacking cuz his dead [guid=%u]", i_pet
.GetGUIDLow());
106 i_pet
.GetMotionMaster()->Clear();
107 i_pet
.GetMotionMaster()->MoveIdle();
109 i_pet
.getHostilRefManager().deleteReferences();
114 Unit
* owner
= i_pet
.GetCharmerOrOwner();
116 if(owner
&& i_pet
.GetCharmInfo() && i_pet
.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW
))
118 i_pet
.GetMotionMaster()->MoveFollow(owner
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
122 i_pet
.clearUnitState(UNIT_STAT_FOLLOW
);
123 i_pet
.GetMotionMaster()->Clear();
124 i_pet
.GetMotionMaster()->MoveIdle();
129 void PetAI::UpdateAI(const uint32 diff
)
131 if (!i_pet
.isAlive())
134 Unit
* owner
= i_pet
.GetCharmerOrOwner();
136 if(m_updateAlliesTimer
<= diff
)
137 // UpdateAllies self set update timer
140 m_updateAlliesTimer
-= diff
;
142 if (inCombat
&& !i_pet
.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( i_pet
.getVictim() )
150 DEBUG_LOG("Pet AI stoped attacking [guid=%u]", i_pet
.GetGUIDLow());
154 else if( i_pet
.IsStopped() || i_pet
.IsWithinDistInMap(i_pet
.getVictim(), ATTACK_DISTANCE
))
156 // required to be stopped cases
157 if ( i_pet
.IsStopped() && i_pet
.IsNonMeleeSpellCasted(false) )
159 if( i_pet
.hasUnitState(UNIT_STAT_FOLLOW
) )
160 i_pet
.InterruptNonMeleeSpells(false);
164 // not required to be stopped case
165 else if( i_pet
.isAttackReady() && i_pet
.canReachWithAttack(i_pet
.getVictim()) )
167 i_pet
.AttackerStateUpdate(i_pet
.getVictim());
169 i_pet
.resetAttackTimer();
171 if ( !i_pet
.getVictim() )
174 //if pet misses its target, it will also be the first in threat list
175 i_pet
.getVictim()->AddThreat(&i_pet
,0.0f
);
182 else if(owner
&& i_pet
.GetCharmInfo())
184 if(owner
->isInCombat() && !(i_pet
.GetCharmInfo()->HasReactState(REACT_PASSIVE
) || i_pet
.GetCharmInfo()->HasCommandState(COMMAND_STAY
)))
186 AttackStart(owner
->getAttackerForHelper());
188 else if(i_pet
.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW
))
190 if (!i_pet
.hasUnitState(UNIT_STAT_FOLLOW
) )
192 i_pet
.GetMotionMaster()->MoveFollow(owner
,PET_FOLLOW_DIST
,PET_FOLLOW_ANGLE
);
197 if (i_pet
.GetGlobalCooldown() == 0 && !i_pet
.IsNonMeleeSpellCasted(false))
200 for (uint8 i
= 0; i
< i_pet
.GetPetAutoSpellSize(); i
++)
202 uint32 spellID
= i_pet
.GetPetAutoSpellOnPos(i
);
206 SpellEntry
const *spellInfo
= sSpellStore
.LookupEntry(spellID
);
210 // ignore some combinations of combat state and combat/noncombat spells
213 if (!IsPositiveSpell(spellInfo
->Id
))
218 if (IsNonCombatSpell(spellInfo
))
222 Spell
*spell
= new Spell(&i_pet
, spellInfo
, false, 0);
224 if(inCombat
&& !i_pet
.hasUnitState(UNIT_STAT_FOLLOW
) && spell
->CanAutoCast(i_pet
.getVictim()))
226 m_targetSpellStore
.push_back(std::make_pair
<Unit
*, Spell
*>(i_pet
.getVictim(), spell
));
231 bool spellUsed
= false;
232 for(std::set
<uint64
>::iterator tar
= m_AllySet
.begin(); tar
!= m_AllySet
.end(); ++tar
)
234 Unit
* Target
= ObjectAccessor::GetUnit(i_pet
,*tar
);
236 //only buff targets that are in combat, unless the spell can only be cast while out of combat
240 if(spell
->CanAutoCast(Target
))
242 m_targetSpellStore
.push_back(std::make_pair
<Unit
*, Spell
*>(Target
, spell
));
252 //found units to cast on to
253 if(!m_targetSpellStore
.empty())
255 uint32 index
= urand(0, m_targetSpellStore
.size() - 1);
257 Spell
* spell
= m_targetSpellStore
[index
].second
;
258 Unit
* target
= m_targetSpellStore
[index
].first
;
260 m_targetSpellStore
.erase(m_targetSpellStore
.begin() + index
);
262 SpellCastTargets targets
;
263 targets
.setUnitTarget( target
);
265 if( !i_pet
.HasInArc(M_PI
, target
) )
267 i_pet
.SetInFront(target
);
268 if( target
->GetTypeId() == TYPEID_PLAYER
)
269 i_pet
.SendUpdateToPlayer( (Player
*)target
);
271 if(owner
&& owner
->GetTypeId() == TYPEID_PLAYER
)
272 i_pet
.SendUpdateToPlayer( (Player
*)owner
);
275 i_pet
.AddCreatureSpellCooldown(spell
->m_spellInfo
->Id
);
277 ((Pet
*)&i_pet
)->CheckLearning(spell
->m_spellInfo
->Id
);
279 spell
->prepare(&targets
);
281 while (!m_targetSpellStore
.empty())
283 delete m_targetSpellStore
.begin()->second
;
284 m_targetSpellStore
.erase(m_targetSpellStore
.begin());
289 bool PetAI::_isVisible(Unit
*u
) const
291 //return false; //( ((Creature*)&i_pet)->GetDistanceSq(u) * 1.0<= sWorld.getConfig(CONFIG_SIGHT_GUARDER) && !u->m_stealth && u->isAlive());
292 return i_pet
.GetDistance(u
) < sWorld
.getConfig(CONFIG_SIGHT_GUARDER
)
293 && u
->isVisibleForOrDetect(&i_pet
,true);
296 void PetAI::UpdateAllies()
298 Unit
* owner
= i_pet
.GetCharmerOrOwner();
299 Group
*pGroup
= NULL
;
301 m_updateAlliesTimer
= 10*IN_MILISECONDS
; //update friendly targets every 10 seconds, lesser checks increase performance
305 else if(owner
->GetTypeId() == TYPEID_PLAYER
)
306 pGroup
= ((Player
*)owner
)->GetGroup();
308 //only pet and owner/not in group->ok
309 if(m_AllySet
.size() == 2 && !pGroup
)
311 //owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
312 if(pGroup
&& !pGroup
->isRaidGroup() && m_AllySet
.size() == (pGroup
->GetMembersCount() + 2))
316 m_AllySet
.insert(i_pet
.GetGUID());
317 if(pGroup
) //add group
319 for(GroupReference
*itr
= pGroup
->GetFirstMember(); itr
!= NULL
; itr
= itr
->next())
321 Player
* Target
= itr
->getSource();
322 if(!Target
|| !pGroup
->SameSubGroup((Player
*)owner
, Target
))
325 if(Target
->GetGUID() == owner
->GetGUID())
328 m_AllySet
.insert(Target
->GetGUID());
332 m_AllySet
.insert(owner
->GetGUID());
335 void PetAI::AttackedBy(Unit
*attacker
)
337 //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
338 if(!i_pet
.getVictim() && i_pet
.GetCharmInfo() && !i_pet
.GetCharmInfo()->HasReactState(REACT_PASSIVE
) &&
339 (!i_pet
.GetCharmInfo()->HasCommandState(COMMAND_STAY
) || i_pet
.canReachWithAttack(attacker
)))
340 AttackStart(attacker
);