root/trunk/src/game/PetAI.cpp @ 156

Revision 156, 11.1 kB (checked in by yumileroy, 17 years ago)

[svn] Use IsWithinCombatDist? for all AI. This should fix the bug that pet and guard does not attack target. (Note: some scriptedAI may need to update)

Original author: megamage
Date: 2008-11-03 14:11:09-06:00

Line 
1/*
2 * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
3 *
4 * Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#include "PetAI.h"
22#include "Errors.h"
23#include "Pet.h"
24#include "Player.h"
25#include "Database/DBCStores.h"
26#include "Spell.h"
27#include "ObjectAccessor.h"
28#include "SpellMgr.h"
29#include "Creature.h"
30#include "World.h"
31#include "Util.h"
32
33int PetAI::Permissible(const Creature *creature)
34{
35    if( creature->isPet())
36        return PERMIT_BASE_SPECIAL;
37
38    return PERMIT_BASE_NO;
39}
40
41PetAI::PetAI(Creature &c) : i_pet(c), i_tracker(TIME_INTERVAL_LOOK), inCombat(false)
42{
43    m_AllySet.clear();
44    UpdateAllies();
45}
46
47void PetAI::MoveInLineOfSight(Unit *u)
48{
49    if( !i_pet.getVictim() && i_pet.GetCharmInfo() &&
50        i_pet.GetCharmInfo()->HasReactState(REACT_AGGRESSIVE) &&
51        u->isTargetableForAttack() && i_pet.IsHostileTo( u ) &&
52        u->isInAccessablePlaceFor(&i_pet))
53    {
54        float attackRadius = i_pet.GetAttackDistance(u);
55        if(i_pet.IsWithinDistInMap(u, attackRadius) && i_pet.GetDistanceZ(u) <= CREATURE_Z_ATTACK_RANGE)
56        {
57            if(i_pet.IsWithinLOSInMap(u))
58            {
59                AttackStart(u);
60                u->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
61            }
62        }
63    }
64}
65
66void PetAI::AttackStart(Unit *u)
67{
68    if( inCombat || !u || (i_pet.isPet() && ((Pet&)i_pet).getPetType() == MINI_PET) )
69        return;
70
71    if(i_pet.Attack(u,true))
72    {
73        i_pet.clearUnitState(UNIT_STAT_FOLLOW);
74        // TMGs call CreatureRelocation which via MoveInLineOfSight can call this function
75        // thus with the following clear the original TMG gets invalidated and crash, doh
76        // hope it doesn't start to leak memory without this :-/
77        //i_pet->Clear();
78        i_pet.GetMotionMaster()->MoveChase(u);
79        inCombat = true;
80    }
81}
82
83void PetAI::EnterEvadeMode()
84{
85}
86
87bool PetAI::IsVisible(Unit *pl) const
88{
89    return _isVisible(pl);
90}
91
92bool PetAI::_needToStop() const
93{
94    // This is needed for charmed creatures, as once their target was reset other effects can trigger threat
95    if(i_pet.isCharmed() && i_pet.getVictim() == i_pet.GetCharmer())
96        return true;
97
98    return !i_pet.getVictim()->isTargetableForAttack();
99}
100
101void PetAI::_stopAttack()
102{
103    inCombat = false;
104    if( !i_pet.isAlive() )
105    {
106        DEBUG_LOG("Creature stoped attacking cuz his dead [guid=%u]", i_pet.GetGUIDLow());
107        i_pet.StopMoving();
108        i_pet.GetMotionMaster()->Clear();
109        i_pet.GetMotionMaster()->MoveIdle();
110        i_pet.CombatStop();
111        i_pet.getHostilRefManager().deleteReferences();
112
113        return;
114    }
115
116    Unit* owner = i_pet.GetCharmerOrOwner();
117
118    if(owner && i_pet.GetCharmInfo() && i_pet.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
119    {
120        i_pet.GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE);
121    }
122    else
123    {
124        i_pet.clearUnitState(UNIT_STAT_FOLLOW);
125        i_pet.GetMotionMaster()->Clear();
126        i_pet.GetMotionMaster()->MoveIdle();
127    }
128    i_pet.AttackStop();
129}
130
131void PetAI::UpdateAI(const uint32 diff)
132{
133    if (!i_pet.isAlive())
134        return;
135
136    Unit* owner = i_pet.GetCharmerOrOwner();
137
138    if(m_updateAlliesTimer <= diff)
139        // UpdateAllies self set update timer
140        UpdateAllies();
141    else
142        m_updateAlliesTimer -= diff;
143
144    if (inCombat && i_pet.getVictim() == NULL)
145        _stopAttack();
146
147    // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc.
148    if( i_pet.getVictim() != NULL )
149    {
150        if( _needToStop() )
151        {
152            DEBUG_LOG("Pet AI stoped attacking [guid=%u]", i_pet.GetGUIDLow());
153            _stopAttack();
154            return;
155        }
156        else if( i_pet.IsStopped() || i_pet.IsWithinDistInMap(i_pet.getVictim(), ATTACK_DISTANCE))
157        {
158            // required to be stopped cases
159            if ( i_pet.IsStopped() && i_pet.IsNonMeleeSpellCasted(false) )
160            {
161                if( i_pet.hasUnitState(UNIT_STAT_FOLLOW) )
162                    i_pet.InterruptNonMeleeSpells(false);
163                else
164                    return;
165            }
166            // not required to be stopped case
167            else if( i_pet.isAttackReady() && i_pet.IsWithinCombatDist(i_pet.getVictim(), ATTACK_DISTANCE) )
168            {
169                i_pet.AttackerStateUpdate(i_pet.getVictim());
170
171                i_pet.resetAttackTimer();
172
173                if ( !i_pet.getVictim() )
174                    return;
175
176                //if pet misses its target, it will also be the first in threat list
177                i_pet.getVictim()->AddThreat(&i_pet,0.0f);
178
179                if( _needToStop() )
180                    _stopAttack();
181            }
182        }
183    }
184    else if(owner && i_pet.GetCharmInfo())
185    {
186        if(owner->isInCombat() && !(i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE) || i_pet.GetCharmInfo()->HasCommandState(COMMAND_STAY)))
187        {
188            AttackStart(owner->getAttackerForHelper());
189        }
190        else if(i_pet.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
191        {
192            if (!i_pet.hasUnitState(UNIT_STAT_FOLLOW) )
193            {
194                i_pet.GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE);
195            }
196        }
197    }
198
199    if (i_pet.GetGlobalCooldown() == 0 && !i_pet.IsNonMeleeSpellCasted(false))
200    {
201        //Autocast
202        for (uint8 i = 0; i < i_pet.GetPetAutoSpellSize(); i++)
203        {
204            uint32 spellID = i_pet.GetPetAutoSpellOnPos(i);
205            if (!spellID)
206                continue;
207
208            SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID);
209            if (!spellInfo)
210                continue;
211
212            // ignore some combinations of combat state and combat/noncombat spells
213            if (!inCombat)
214            {
215                if (!IsPositiveSpell(spellInfo->Id))
216                    continue;
217            }
218            else
219            {
220                if (IsNonCombatSpell(spellInfo))
221                    continue;
222            }
223
224            Spell *spell = new Spell(&i_pet, spellInfo, false, 0);
225
226            if(inCombat && !i_pet.hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(i_pet.getVictim()))
227            {
228                m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(i_pet.getVictim(), spell));
229                continue;
230            }
231            else
232            {
233                bool spellUsed = false;
234                for(std::set<uint64>::iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
235                {
236                    Unit* Target = ObjectAccessor::GetUnit(i_pet,*tar);
237
238                    //only buff targets that are in combat, unless the spell can only be cast while out of combat
239                    if(!Target)
240                        continue;
241
242                    if(spell->CanAutoCast(Target))
243                    {
244                        m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell));
245                        spellUsed = true;
246                        break;
247                    }
248                }
249                if (!spellUsed)
250                    delete spell;
251            }
252        }
253
254        //found units to cast on to
255        if(!m_targetSpellStore.empty())
256        {
257            uint32 index = urand(0, m_targetSpellStore.size() - 1);
258
259            Spell* spell  = m_targetSpellStore[index].second;
260            Unit*  target = m_targetSpellStore[index].first;
261
262            m_targetSpellStore.erase(m_targetSpellStore.begin() + index);
263
264            SpellCastTargets targets;
265            targets.setUnitTarget( target );
266
267            if( !i_pet.HasInArc(M_PI, target) )
268            {
269                i_pet.SetInFront(target);
270                if( target->GetTypeId() == TYPEID_PLAYER )
271                    i_pet.SendUpdateToPlayer( (Player*)target );
272
273                if(owner && owner->GetTypeId() == TYPEID_PLAYER)
274                    i_pet.SendUpdateToPlayer( (Player*)owner );
275            }
276
277            i_pet.AddCreatureSpellCooldown(spell->m_spellInfo->Id);
278            if(i_pet.isPet())
279                ((Pet*)&i_pet)->CheckLearning(spell->m_spellInfo->Id);
280
281            spell->prepare(&targets);
282        }
283        while (!m_targetSpellStore.empty())
284        {
285            delete m_targetSpellStore.begin()->second;
286            m_targetSpellStore.erase(m_targetSpellStore.begin());
287        }
288    }
289}
290
291bool PetAI::_isVisible(Unit *u) const
292{
293    //return false;                                           //( ((Creature*)&i_pet)->GetDistanceSq(u) * 1.0<= sWorld.getConfig(CONFIG_SIGHT_GUARDER) && !u->m_stealth && u->isAlive());
294    return i_pet.GetDistance(u) < sWorld.getConfig(CONFIG_SIGHT_GUARDER)
295        && u->isVisibleForOrDetect(&i_pet,true);
296}
297
298void PetAI::UpdateAllies()
299{
300    Unit* owner = i_pet.GetCharmerOrOwner();
301    Group *pGroup = NULL;
302
303    m_updateAlliesTimer = 10000;                            //update friendly targets every 10 seconds, lesser checks increase performance
304
305    if(!owner)
306        return;
307    else if(owner->GetTypeId() == TYPEID_PLAYER)
308        pGroup = ((Player*)owner)->GetGroup();
309
310    //only pet and owner/not in group->ok
311    if(m_AllySet.size() == 2 && !pGroup)
312        return;
313    //owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
314    if(pGroup && !pGroup->isRaidGroup() && m_AllySet.size() == (pGroup->GetMembersCount() + 2))
315        return;
316
317    m_AllySet.clear();
318    m_AllySet.insert(i_pet.GetGUID());
319    if(pGroup)                                              //add group
320    {
321        for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
322        {
323            Player* Target = itr->getSource();
324            if(!Target || !pGroup->SameSubGroup((Player*)owner, Target))
325                continue;
326
327            if(Target->GetGUID() == owner->GetGUID())
328                continue;
329
330            m_AllySet.insert(Target->GetGUID());
331        }
332    }
333    else                                                    //remove group
334        m_AllySet.insert(owner->GetGUID());
335}
336
337void PetAI::AttackedBy(Unit *attacker)
338{
339    //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
340    if(!i_pet.getVictim() && i_pet.GetCharmInfo() && !i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE) &&
341        (!i_pet.GetCharmInfo()->HasCommandState(COMMAND_STAY) || i_pet.canReachWithAttack(attacker)))
342        AttackStart(attacker);
343}
Note: See TracBrowser for help on using the browser.