[7978] Pets now can be send to fight another target even if pet in combat with some...
[getmangos.git] / src / game / PetAI.cpp
blobcd0e1db02ae7b45e63659b92be937b07f202b68b
1 /*
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
19 #include "PetAI.h"
20 #include "Errors.h"
21 #include "Pet.h"
22 #include "Player.h"
23 #include "DBCStores.h"
24 #include "Spell.h"
25 #include "ObjectAccessor.h"
26 #include "SpellMgr.h"
27 #include "Creature.h"
28 #include "World.h"
29 #include "Util.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)
41 m_AllySet.clear();
42 UpdateAllies();
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))
57 AttackStart(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))
67 return;
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 :-/
75 //i_pet->Clear();
76 m_creature->GetMotionMaster()->MoveChase(u);
77 inCombat = true;
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())
94 return true;
96 return !m_creature->getVictim()->isTargetableForAttack();
99 void PetAI::_stopAttack()
101 inCombat = false;
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();
111 return;
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);
120 else
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())
132 return;
134 Unit* owner = m_creature->GetCharmerOrOwner();
136 if(m_updateAlliesTimer <= diff)
137 // UpdateAllies self set update timer
138 UpdateAllies();
139 else
140 m_updateAlliesTimer -= diff;
142 if (inCombat && !m_creature->getVictim())
143 _stopAttack();
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())
148 if (_needToStop())
150 DEBUG_LOG("Pet AI stoped attacking [guid=%u]", m_creature->GetGUIDLow());
151 _stopAttack();
152 return;
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);
161 else
162 return;
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())
172 return;
174 //if pet misses its target, it will also be the first in threat list
175 m_creature->getVictim()->AddThreat(m_creature,0.0f);
177 if( _needToStop() )
178 _stopAttack();
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 if (m_creature->GetGlobalCooldown() == 0 && !m_creature->IsNonMeleeSpellCasted(false))
199 //Autocast
200 for (uint8 i = 0; i < m_creature->GetPetAutoSpellSize(); ++i)
202 uint32 spellID = m_creature->GetPetAutoSpellOnPos(i);
203 if (!spellID)
204 continue;
206 SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID);
207 if (!spellInfo)
208 continue;
210 // ignore some combinations of combat state and combat/noncombat spells
211 if (!inCombat)
213 if (!IsPositiveSpell(spellInfo->Id))
214 continue;
216 else
218 if (IsNonCombatSpell(spellInfo))
219 continue;
222 Spell *spell = new Spell(m_creature, spellInfo, false, 0);
224 if (inCombat && !m_creature->hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(m_creature->getVictim()))
226 m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(m_creature->getVictim(), spell));
227 continue;
229 else
231 bool spellUsed = false;
232 for(std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
234 Unit* Target = ObjectAccessor::GetUnit(*m_creature,*tar);
236 //only buff targets that are in combat, unless the spell can only be cast while out of combat
237 if(!Target)
238 continue;
240 if(spell->CanAutoCast(Target))
242 m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell));
243 spellUsed = true;
244 break;
247 if (!spellUsed)
248 delete 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 (!m_creature->HasInArc(M_PI, target))
267 m_creature->SetInFront(target);
268 if (target->GetTypeId() == TYPEID_PLAYER)
269 m_creature->SendUpdateToPlayer((Player*)target);
271 if (owner && owner->GetTypeId() == TYPEID_PLAYER)
272 m_creature->SendUpdateToPlayer( (Player*)owner );
275 m_creature->AddCreatureSpellCooldown(spell->m_spellInfo->Id);
277 spell->prepare(&targets);
279 while (!m_targetSpellStore.empty())
281 delete m_targetSpellStore.begin()->second;
282 m_targetSpellStore.erase(m_targetSpellStore.begin());
287 bool PetAI::_isVisible(Unit *u) const
289 return m_creature->IsWithinDist(u,sWorld.getConfig(CONFIG_SIGHT_GUARDER))
290 && u->isVisibleForOrDetect(m_creature,true);
293 void PetAI::UpdateAllies()
295 Unit* owner = m_creature->GetCharmerOrOwner();
296 Group *pGroup = NULL;
298 m_updateAlliesTimer = 10*IN_MILISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
300 if(!owner)
301 return;
302 else if(owner->GetTypeId() == TYPEID_PLAYER)
303 pGroup = ((Player*)owner)->GetGroup();
305 //only pet and owner/not in group->ok
306 if(m_AllySet.size() == 2 && !pGroup)
307 return;
308 //owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
309 if(pGroup && !pGroup->isRaidGroup() && m_AllySet.size() == (pGroup->GetMembersCount() + 2))
310 return;
312 m_AllySet.clear();
313 m_AllySet.insert(m_creature->GetGUID());
314 if(pGroup) //add group
316 for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
318 Player* Target = itr->getSource();
319 if(!Target || !pGroup->SameSubGroup((Player*)owner, Target))
320 continue;
322 if(Target->GetGUID() == owner->GetGUID())
323 continue;
325 m_AllySet.insert(Target->GetGUID());
328 else //remove group
329 m_AllySet.insert(owner->GetGUID());
332 void PetAI::AttackedBy(Unit *attacker)
334 //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
335 if(!m_creature->getVictim() && m_creature->GetCharmInfo() && !m_creature->GetCharmInfo()->HasReactState(REACT_PASSIVE) &&
336 (!m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY) || m_creature->canReachWithAttack(attacker)))
337 AttackStart(attacker);