Skip to content

Commit 47a621f

Browse files
author
NoFantasy
committed
[1312] Added class for FollowerAI. Note this is under development and may have issues in some situations.
Added class for FollowerAI. Note this is under development and may have issues in some situations. FollowerAI is generally to be used for "escort" quests where NPC follow leader instead of using a predefined path. git-svn-id: https://scriptdev2.svn.sourceforge.net/svnroot/scriptdev2@1312 5f9c896b-1e26-0410-94da-f77f675e2462
1 parent 408cfe3 commit 47a621f

5 files changed

Lines changed: 377 additions & 0 deletions

File tree

Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ ScriptMgr.h \
3232
config.h \
3333
base/escort_ai.cpp \
3434
base/escort_ai.h \
35+
base/follower_ai.cpp \
36+
base/follower_ai.h \
3537
base/guard_ai.cpp \
3638
base/guard_ai.h \
3739
base/simple_ai.cpp \

VC80/80ScriptDev2.vcproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@
369369
RelativePath="..\base\escort_ai.h"
370370
>
371371
</File>
372+
<File
373+
RelativePath="..\base\follower_ai.cpp"
374+
>
375+
</File>
376+
<File
377+
RelativePath="..\base\follower_ai.h"
378+
>
379+
</File>
372380
<File
373381
RelativePath="..\base\guard_ai.cpp"
374382
>

VC90/90ScriptDev2.vcproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,14 @@
368368
RelativePath="..\base\escort_ai.h"
369369
>
370370
</File>
371+
<File
372+
RelativePath="..\base\follower_ai.cpp"
373+
>
374+
</File>
375+
<File
376+
RelativePath="..\base\follower_ai.h"
377+
>
378+
</File>
371379
<File
372380
RelativePath="..\base\guard_ai.cpp"
373381
>

base/follower_ai.cpp

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
2+
* This program is free software licensed under GPL version 2
3+
* Please see the included DOCS/LICENSE.TXT for more information */
4+
5+
/* ScriptData
6+
SDName: FollowerAI
7+
SD%Complete: 50
8+
SDComment: This AI is under development
9+
SDCategory: Npc
10+
EndScriptData */
11+
12+
#include "precompiled.h"
13+
#include "follower_ai.h"
14+
15+
const float MAX_PLAYER_DISTANCE = 100.0f;
16+
17+
enum
18+
{
19+
POINT_COMBAT_START = 0xFFFFFF
20+
};
21+
22+
FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature),
23+
m_uiLeaderGUID(0),
24+
m_pQuestForFollow(NULL),
25+
m_uiUpdateFollowTimer(2500),
26+
m_bIsFollowing(false),
27+
m_bIsReturnToLeader(false),
28+
m_bIsFollowComplete(false)
29+
{}
30+
31+
void FollowerAI::AttackStart(Unit* pWho)
32+
{
33+
if (!pWho)
34+
return;
35+
36+
if (m_creature->Attack(pWho, true))
37+
{
38+
m_creature->AddThreat(pWho, 0.0f);
39+
m_creature->SetInCombatWith(pWho);
40+
pWho->SetInCombatWith(m_creature);
41+
42+
if (m_creature->hasUnitState(UNIT_STAT_FOLLOW))
43+
m_creature->clearUnitState(UNIT_STAT_FOLLOW);
44+
45+
if (IsCombatMovement())
46+
m_creature->GetMotionMaster()->MoveChase(pWho);
47+
}
48+
}
49+
50+
void FollowerAI::MoveInLineOfSight(Unit* pWho)
51+
{
52+
if (!m_creature->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() &&
53+
m_creature->IsHostileTo(pWho) && pWho->isInAccessablePlaceFor(m_creature))
54+
{
55+
if (!m_creature->canFly() && m_creature->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
56+
return;
57+
58+
//This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range
59+
//It will cause m_creature to attack pWho that are attacking _any_ player (which has been confirmed may happen also on offi)
60+
//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
61+
if (m_creature->hasUnitState(UNIT_STAT_FOLLOW) &&
62+
m_creature->GetCreatureInfo()->type_flags & 0x01000 &&
63+
pWho->getVictim() &&
64+
pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself() &&
65+
m_creature->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) &&
66+
m_creature->IsWithinLOSInMap(pWho))
67+
{
68+
pWho->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
69+
AttackStart(pWho);
70+
}
71+
else
72+
{
73+
float attackRadius = m_creature->GetAttackDistance(pWho);
74+
if (m_creature->IsWithinDistInMap(pWho, attackRadius) && m_creature->IsWithinLOSInMap(pWho))
75+
{
76+
if (!m_creature->getVictim())
77+
{
78+
pWho->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
79+
AttackStart(pWho);
80+
}
81+
else if (m_creature->GetMap()->IsDungeon())
82+
{
83+
pWho->SetInCombatWith(m_creature);
84+
m_creature->AddThreat(pWho, 0.0f);
85+
}
86+
}
87+
}
88+
}
89+
}
90+
91+
void FollowerAI::JustDied(Unit* pKiller)
92+
{
93+
if (!m_bIsFollowing || !m_uiLeaderGUID)
94+
return;
95+
96+
//TODO: need a better check for quests with time limit.
97+
if (Player* pPlayer = GetLeaderForFollower())
98+
{
99+
if (Group* pGroup = pPlayer->GetGroup())
100+
{
101+
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
102+
{
103+
if (Player* pMember = pRef->getSource())
104+
{
105+
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
106+
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
107+
}
108+
}
109+
}
110+
else
111+
{
112+
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
113+
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
114+
}
115+
}
116+
}
117+
118+
void FollowerAI::JustRespawned()
119+
{
120+
m_bIsFollowing = false;
121+
m_bIsReturnToLeader = false;
122+
m_bIsFollowComplete = false;
123+
124+
if (!IsCombatMovement())
125+
SetCombatMovement(true);
126+
127+
if (m_creature->getFaction() != m_creature->GetCreatureInfo()->faction_A)
128+
m_creature->setFaction(m_creature->GetCreatureInfo()->faction_A);
129+
130+
Reset();
131+
}
132+
133+
void FollowerAI::EnterEvadeMode()
134+
{
135+
m_creature->RemoveAllAuras();
136+
m_creature->DeleteThreatList();
137+
m_creature->CombatStop(true);
138+
m_creature->SetLootRecipient(NULL);
139+
140+
if (m_bIsFollowing)
141+
{
142+
debug_log("SD2: FollowerAI left combat, returning to CombatStartPosition.");
143+
144+
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
145+
{
146+
float fPosX, fPosY, fPosZ;
147+
m_creature->GetCombatStartPosition(fPosX, fPosY, fPosZ);
148+
m_creature->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
149+
}
150+
}
151+
else
152+
{
153+
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
154+
m_creature->GetMotionMaster()->MoveTargetedHome();
155+
}
156+
157+
Reset();
158+
}
159+
160+
void FollowerAI::UpdateAI(const uint32 uiDiff)
161+
{
162+
if (m_bIsFollowing && !m_creature->getVictim())
163+
{
164+
if (m_uiUpdateFollowTimer < uiDiff)
165+
{
166+
if (m_bIsFollowComplete)
167+
{
168+
debug_log("SD2: FollowerAI is set completed, despawns.");
169+
m_creature->ForcedDespawn();
170+
return;
171+
}
172+
173+
bool bIsMaxRangeExceeded = true;
174+
175+
if (Player* pPlayer = GetLeaderForFollower())
176+
{
177+
if (m_bIsReturnToLeader)
178+
{
179+
debug_log("SD2: FollowerAI is returning to leader.");
180+
m_creature->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
181+
m_bIsReturnToLeader = false;
182+
return;
183+
}
184+
185+
if (Group* pGroup = pPlayer->GetGroup())
186+
{
187+
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
188+
{
189+
Player* pMember = pRef->getSource();
190+
191+
if (pMember && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
192+
{
193+
bIsMaxRangeExceeded = false;
194+
break;
195+
}
196+
}
197+
}
198+
else
199+
{
200+
if (m_creature->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE))
201+
bIsMaxRangeExceeded = false;
202+
}
203+
}
204+
205+
if (bIsMaxRangeExceeded)
206+
{
207+
debug_log("SD2: FollowerAI failed because player/group was to far away or not found");
208+
m_creature->ForcedDespawn();
209+
return;
210+
}
211+
212+
m_uiUpdateFollowTimer = 1000;
213+
}
214+
else
215+
m_uiUpdateFollowTimer -= uiDiff;
216+
}
217+
218+
UpdateFollowerAI(uiDiff);
219+
}
220+
221+
void FollowerAI::UpdateFollowerAI(const uint32 uiDiff)
222+
{
223+
if (!m_creature->SelectHostilTarget() || !m_creature->getVictim())
224+
return;
225+
226+
DoMeleeAttackIfReady();
227+
}
228+
229+
void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId)
230+
{
231+
if (uiMotionType != POINT_MOTION_TYPE || !m_bIsFollowing)
232+
return;
233+
234+
if (uiPointId == POINT_COMBAT_START)
235+
{
236+
if (GetLeaderForFollower())
237+
m_bIsReturnToLeader = true;
238+
else
239+
m_creature->ForcedDespawn();
240+
}
241+
}
242+
243+
void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest)
244+
{
245+
if (m_creature->getVictim())
246+
{
247+
debug_log("SD2: FollowerAI attempt to StartFollow while in combat.");
248+
return;
249+
}
250+
251+
if (m_bIsFollowing)
252+
{
253+
error_log("SD2: FollowerAI attempt to StartFollow while already following.");
254+
return;
255+
}
256+
257+
//set variables
258+
m_uiLeaderGUID = pLeader->GetGUID();
259+
260+
if (uiFactionForFollower)
261+
m_creature->setFaction(uiFactionForFollower);
262+
263+
m_pQuestForFollow = pQuest;
264+
265+
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
266+
{
267+
m_creature->GetMotionMaster()->Clear();
268+
m_creature->GetMotionMaster()->MoveIdle();
269+
debug_log("SD2: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
270+
}
271+
272+
m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
273+
274+
m_creature->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
275+
276+
m_bIsFollowing = true;
277+
278+
debug_log("SD2: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID);
279+
}
280+
281+
Player* FollowerAI::GetLeaderForFollower()
282+
{
283+
if (Player* pLeader = (Player*)Unit::GetUnit(*m_creature, m_uiLeaderGUID))
284+
{
285+
if (pLeader->isAlive())
286+
return pLeader;
287+
else
288+
{
289+
if (Group* pGroup = pLeader->GetGroup())
290+
{
291+
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
292+
{
293+
Player* pMember = pRef->getSource();
294+
295+
if (pMember && pMember->isAlive() && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
296+
{
297+
debug_log("SD2: FollowerAI GetLeader changed and returned new leader.");
298+
m_uiLeaderGUID = pMember->GetGUID();
299+
return pMember;
300+
break;
301+
}
302+
}
303+
}
304+
}
305+
}
306+
307+
debug_log("SD2: FollowerAI GetLeader can not find suitable leader.");
308+
return NULL;
309+
}

base/follower_ai.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
2+
* This program is free software licensed under GPL version 2
3+
* Please see the included DOCS/LICENSE.TXT for more information */
4+
5+
#ifndef SC_FOLLOWERAI_H
6+
#define SC_FOLLOWERAI_H
7+
8+
class MANGOS_DLL_DECL FollowerAI : public ScriptedAI
9+
{
10+
public:
11+
explicit FollowerAI(Creature* pCreature);
12+
~FollowerAI() {}
13+
14+
//virtual void WaypointReached(uint32 uiPointId) = 0;
15+
16+
void MovementInform(uint32 uiMotionType, uint32 uiPointId);
17+
18+
void AttackStart(Unit*);
19+
20+
void MoveInLineOfSight(Unit*);
21+
22+
void EnterEvadeMode();
23+
24+
void JustDied(Unit*);
25+
26+
void JustRespawned();
27+
28+
void UpdateAI(const uint32); //the "internal" update, calls UpdateFollowerAI()
29+
virtual void UpdateFollowerAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
30+
31+
void StartFollow(Player* pPlayer, uint32 uiFactionForFollower = 0, const Quest* pQuest = NULL);
32+
33+
protected:
34+
void SetFollowComplete() { m_bIsFollowComplete = true; }
35+
bool IsFollowComplete() { return m_bIsFollowComplete; }
36+
37+
Player* GetLeaderForFollower();
38+
39+
private:
40+
uint64 m_uiLeaderGUID;
41+
uint32 m_uiUpdateFollowTimer;
42+
43+
bool m_bIsFollowing;
44+
bool m_bIsReturnToLeader;
45+
bool m_bIsFollowComplete;
46+
47+
const Quest* m_pQuestForFollow; //normally we have a quest
48+
};
49+
50+
#endif

0 commit comments

Comments
 (0)