TrinityCore
Loading...
Searching...
No Matches
ScriptedEscortAI.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ScriptedEscortAI.h"
19#include "Creature.h"
20#include "Group.h"
21#include "Log.h"
22#include "Map.h"
23#include "MotionMaster.h"
24#include "MovementGenerator.h"
25#include "ObjectAccessor.h"
26#include "Player.h"
27#include "ScriptSystem.h"
28#include "WaypointManager.h"
29#include "World.h"
30
32{
33 POINT_LAST_POINT = 0xFFFFFF,
34 POINT_HOME = 0xFFFFFE
35};
36
37EscortAI::EscortAI(Creature* creature) noexcept : ScriptedAI(creature), _pauseTimer(2500ms), _playerCheckTimer(1000), _escortState(STATE_ESCORT_NONE), _maxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE),
38 _escortQuest(nullptr), _activeAttacker(true), _instantRespawn(false), _returnToStart(false), _despawnAtEnd(true), _despawnAtFar(true),
39 _hasImmuneToNPCFlags(false), _started(false), _ended(false), _resume(false)
40{
41}
42
44{
45 if (!who)
46 return;
47
49 return;
50
52}
53
54void EscortAI::JustDied(Unit* /*killer*/)
55{
57 return;
58
59 if (Player* player = GetPlayerForEscort())
60 {
61 if (Group* group = player->GetGroup())
62 {
63 for (GroupReference const& groupRef : group->GetMembers())
64 if (groupRef.GetSource()->IsInMap(player))
65 groupRef.GetSource()->FailQuest(_escortQuest->GetQuestId());
66 }
67 else
68 player->FailQuest(_escortQuest->GetQuestId());
69 }
70}
71
73{
75
78
79 // add a small delay before going to first waypoint, normal in near all cases
80 _pauseTimer = 2s;
81
84
85 Reset();
86}
87
92
94{
96 me->CombatStop(true);
98 me->SetTappedBy(nullptr);
99
101
103 {
106 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::EnterEvadeMode: left combat and is now returning to last point ({})", me->GetGUID().ToString());
107 }
108 else
109 {
112 me->SetImmuneToNPC(true);
113 Reset();
114 }
115}
116
118{
119 // no action allowed if there is no escort
121 return;
122
123 if (type == POINT_MOTION_TYPE)
124 {
125 if (_pauseTimer == 0s)
126 _pauseTimer = 2s;
127
128 // continue waypoint movement
129 if (id == POINT_LAST_POINT)
130 {
131 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::MovementInform: returned to before combat position ({})", me->GetGUID().ToString());
132 me->SetWalk(false);
134 }
135 else if (id == POINT_HOME)
136 {
137 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::MovementInform: returned to home location and restarting waypoint path ({})", me->GetGUID().ToString());
138 _started = false;
139 }
140 }
141 else if (type == WAYPOINT_MOTION_TYPE)
142 {
143 ASSERT(id < _path.Nodes.size(), "EscortAI::MovementInform: referenced movement id (%u) points to non-existing node in loaded path (%s)", id, me->GetGUID().ToString().c_str());
144 WaypointNode waypoint = _path.Nodes[id];
145
146 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::MovementInform: waypoint node {} reached ({})", waypoint.Id, me->GetGUID().ToString());
147
148 // last point
149 if (id == _path.Nodes.size() - 1)
150 {
151 _started = false;
152 _ended = true;
153 _pauseTimer = 1s;
154 }
155 }
156}
157
159{
160 // Waypoint Updating
162 {
163 if (_pauseTimer.count() <= diff)
164 {
166 {
167 _pauseTimer = 0s;
168
169 if (_ended)
170 {
171 _ended = false;
173
174 if (_despawnAtEnd)
175 {
176 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::UpdateAI: reached end of waypoints, despawning at end ({})", me->GetGUID().ToString());
177 if (_instantRespawn)
178 me->Respawn(true);
179 else
181 }
182 else if (_returnToStart)
183 {
184 Position respawnPosition = me->GetRespawnPosition();
185 me->GetMotionMaster()->MovePoint(POINT_HOME, respawnPosition);
186 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::UpdateAI: returning to spawn location: {} ({})", respawnPosition, me->GetGUID().ToString());
187 }
188 else
190 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::UpdateAI: reached end of waypoints ({})", me->GetGUID().ToString());
191 return;
192 }
193
194 if (!_started)
195 {
196 _started = true;
197 me->GetMotionMaster()->MovePath(_path, false);
198 }
199 else if (_resume)
200 {
201 _resume = false;
203 movementGenerator->Resume(0);
204 }
205 }
206 }
207 else
208 _pauseTimer -= Milliseconds(diff);
209 }
210
211 // Check if player or any member of his group is within range
213 {
214 if (_playerCheckTimer <= diff)
215 {
217 {
218 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::UpdateAI: failed because player/group was to far away or not found ({})", me->GetGUID().ToString());
219
220 bool isEscort = false;
222 isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (creatureData->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC));
223
224 if (_instantRespawn)
225 {
226 if (!isEscort)
227 me->DespawnOrUnsummon(0s, 1s);
228 else
230 }
231 else
233
234 return;
235 }
236
237 _playerCheckTimer = 1000;
238 }
239 else
240 _playerCheckTimer -= diff;
241 }
242
243 UpdateEscortAI(diff);
244}
245
247{
248 UpdateVictim();
249}
250
251void EscortAI::AddWaypoint(uint32 id, float x, float y, float z, bool run)
252{
253 AddWaypoint(id, x, y, z, 0.0f, {}, run);
254}
255
256void EscortAI::AddWaypoint(uint32 id, float x, float y, float z, float orientation/* = 0*/, Optional<Milliseconds> waitTime/* = {}*/, bool run /*= false*/)
257{
260
261 _path.Nodes.emplace_back(id, x, y, z, orientation, waitTime, run ? WaypointMoveType::Run : WaypointMoveType::Walk);
262}
263
265{
266 _path.Nodes.clear();
267}
268
270{
271 WaypointPath const* path = sWaypointMgr->GetPath(pathId);
272 if (!path)
273 {
274 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::LoadPath: (script: {}) path {} is invalid ({})", me->GetScriptName(), pathId, me->GetGUID().ToString());
275 return;
276 }
277 _path = *path;
278}
279
281void EscortAI::Start(bool isActiveAttacker /* = true*/, ObjectGuid playerGUID /* = 0 */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */)
282{
283 if (_path.Nodes.empty())
284 {
285 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::Start: (script: {}) path is empty ({})", me->GetScriptName(), me->GetGUID().ToString());
286 return;
287 }
288
289 // Queue respawn from the point it starts
290 if (CreatureData const* cdata = me->GetCreatureData())
291 {
292 if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC))
294 }
295
296 if (me->IsEngaged())
297 {
298 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::Start: (script: {}) attempts to Start while in combat ({})", me->GetScriptName(), me->GetGUID().ToString());
299 return;
300 }
301
303 {
304 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::Start: (script: {}) attempts to Start while already escorting ({})", me->GetScriptName(), me->GetGUID().ToString());
305 return;
306 }
307
308 if (_path.Nodes.empty())
309 {
310 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::Start: (script: {}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {}) ({})", me->GetScriptName(), quest ? quest->GetQuestId() : 0, me->GetGUID());
311 return;
312 }
313
314 // set variables
315 _activeAttacker = isActiveAttacker;
316 _playerGUID = playerGUID;
317 _escortQuest = quest;
318 _instantRespawn = instantRespawn;
319 _returnToStart = canLoopPath;
320
322 TC_LOG_ERROR("scripts.ai.escortai", "EscortAI::Start: (script: {}) is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn ({})", me->GetScriptName(), me->GetGUID().ToString());
323
326
327 // disable npcflags
330 if (me->IsImmuneToNPC())
331 {
333 me->SetImmuneToNPC(false);
334 }
335
336 TC_LOG_DEBUG("scripts.ai.escortai", "EscortAI::Start: (script: {}) started with {} waypoints. ActiveAttacker = {}, Player = {} ({})",
338
339 _started = false;
341}
342
344{
346 return;
347
348 if (on)
349 {
352 movementGenerator->Pause(0);
353 }
354 else
355 {
357 _resume = true;
358 }
359}
360
365
366// see followerAI
368{
369 if (!who || !who->GetVictim())
370 return false;
371
373 return false;
374
375 // experimental (unknown) flag not present
377 return false;
378
379 // not a player
381 return false;
382
383 if (!who->isInAccessiblePlaceFor(me))
384 return false;
385
386 if (!CanAIAttack(who))
387 return false;
388
389 // we cannot attack in evade mode
390 if (me->IsInEvadeMode())
391 return false;
392
393 // or if enemy is in evade mode
394 if (who->GetTypeId() == TYPEID_UNIT && who->ToCreature()->IsInEvadeMode())
395 return false;
396
397 if (!me->IsValidAssistTarget(who->GetVictim()))
398 return false;
399
400 // too far away and no free sight
402 {
403 me->EngageWithTarget(who);
404 return true;
405 }
406
407 return false;
408}
409
411{
412 if (Player* player = GetPlayerForEscort())
413 {
414 if (Group* group = player->GetGroup())
415 {
416 for (GroupReference const& groupRef : group->GetMembers())
417 if (me->IsWithinDistInMap(groupRef.GetSource(), GetMaxPlayerDistance()))
418 return true;
419 }
420 else if (me->IsWithinDistInMap(player, GetMaxPlayerDistance()))
421 return true;
422 }
423
424 return false;
425}
uint32_t uint32
Definition Define.h:154
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
#define ASSERT
Definition Errors.h:72
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:173
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:182
@ MOTION_PRIORITY_NORMAL
@ MOTION_SLOT_DEFAULT
@ WAYPOINT_MOTION_TYPE
@ POINT_MOTION_TYPE
@ TYPEID_UNIT
Definition ObjectGuid.h:43
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
@ POINT_HOME
@ POINT_LAST_POINT
@ STATE_ESCORT_PAUSED
@ STATE_ESCORT_ESCORTING
@ STATE_ESCORT_NONE
@ STATE_ESCORT_RETURNING
#define DEFAULT_MAX_PLAYER_DISTANCE
@ CREATURE_TYPE_FLAG_CAN_ASSIST
@ SPAWNGROUP_FLAG_ESCORTQUESTNPC
Definition SpawnData.h:60
@ SPAWN_TYPE_CREATURE
Definition SpawnData.h:36
EvadeReason
@ REACT_PASSIVE
@ UNIT_NPC_FLAG_NONE
@ UNIT_NPC_FLAG_2_NONE
#define sWaypointMgr
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
TypeID GetTypeId() const
Definition BaseEntity.h:166
virtual void MoveInLineOfSight(Unit *)
bool UpdateVictim()
Creature *const me
Definition CreatureAI.h:63
void EngagementOver()
bool IsTapListNotClearedOnEvade() const
Definition Creature.h:315
void Respawn(bool force=false)
CreatureDifficulty const * GetCreatureDifficulty() const
Definition Creature.h:276
void GetHomePosition(float &x, float &y, float &z, float &ori) const
Definition Creature.h:396
bool HasReactState(ReactStates state) const
Definition Creature.h:182
bool IsEngaged() const override
void SetTappedBy(Unit const *unit, bool withGroup=true)
bool IsImmuneToNPC() const
Definition Unit.h:1063
void DespawnOrUnsummon(Milliseconds timeToDespawn=0s, Seconds forceRespawnTime=0s)
CreatureData const * GetCreatureData() const
Definition Creature.h:275
ObjectGuid::LowType GetSpawnId() const
Definition Creature.h:110
Position GetRespawnPosition(float *dist=nullptr) const
CreatureTemplate const * GetCreatureTemplate() const
Definition Creature.h:274
uint32 GetRespawnDelay() const
Definition Creature.h:367
void SetImmuneToNPC(bool apply) override
Definition Creature.h:193
std::string GetScriptName() const
void SaveRespawnTime(uint32 forceDelay=0)
bool IsInEvadeMode() const
Definition Creature.h:224
Definition Group.h:209
void Respawn(RespawnInfo *info, CharacterDatabaseTransaction dbTrans=nullptr)
Definition Map.cpp:2070
void MovePoint(uint32 id, Position const &pos, bool generatePath=true, Optional< float > finalOrient={}, Optional< float > speed={}, MovementWalkRunSpeedSelectionMode speedSelectionMode=MovementWalkRunSpeedSelectionMode::Default, Optional< float > closeEnoughDistance={}, Optional< MovementFadeObject > fadeObject={}, Scripting::v2::ActionResultSetter< MovementStopReason > &&scriptResult={})
MovementGenerator * GetCurrentMovementGenerator() const
void MoveTargetedHome()
void MovePath(uint32 pathId, bool repeatable, Optional< Milliseconds > duration={}, Optional< float > speed={}, MovementWalkRunSpeedSelectionMode speedSelectionMode=MovementWalkRunSpeedSelectionMode::Default, Optional< std::pair< Milliseconds, Milliseconds > > waitTimeRangeAtPathEnd={}, Optional< float > wanderDistanceAtPathEnds={}, Optional< bool > followPathBackwardsFromEndToStart={}, Optional< bool > exactSplinePath={}, bool generatePath=true, Optional< MovementFadeObject > fadeObject={}, Scripting::v2::ActionResultSetter< MovementStopReason > &&scriptResult={})
bool IsEmpty() const
Definition ObjectGuid.h:362
std::string ToString() const
Creature * ToCreature()
Definition Object.h:121
uint32 GetQuestId() const
Definition QuestDef.h:633
virtual void Reset()
Definition UnitAI.h:64
virtual bool CanAIAttack(Unit const *) const
Definition UnitAI.h:58
Definition Unit.h:647
void CombatStop(bool includingCast=false, bool mutualPvP=true, bool(*unitFilter)(Unit const *otherUnit)=nullptr)
Definition Unit.cpp:6031
void ReplaceAllNpcFlags2(NPCFlags2 flags)
Definition Unit.h:1020
void RestoreFaction()
Definition Unit.cpp:12143
MotionMaster * GetMotionMaster()
Definition Unit.h:1750
bool isInAccessiblePlaceFor(Creature const *c) const
Definition Unit.cpp:3328
Unit * EnsureVictim() const
Definition Unit.h:740
uint32 GetFaction() const override
Definition Unit.h:886
bool SetWalk(bool enable)
Definition Unit.cpp:13422
void EngageWithTarget(Unit *who)
Definition Unit.cpp:8513
Unit * GetVictim() const
Definition Unit.h:738
void RemoveAllAuras()
Definition Unit.cpp:4400
void ReplaceAllNpcFlags(NPCFlags flags)
Definition Unit.h:1014
Map * GetMap() const
Definition Object.h:411
bool IsWithinLOSInMap(WorldObject const *obj, LineOfSightChecks checks=LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags ignoreFlags=VMAP::ModelIgnoreFlags::Nothing) const
Definition Object.cpp:533
Player * GetCharmerOrOwnerPlayerOrPlayerItself() const
Definition Object.cpp:1626
bool IsWithinDistInMap(WorldObject const *obj, float dist2compare, bool is3D=true, bool incOwnRadius=true, bool incTargetRadius=true) const
Definition Object.cpp:499
bool IsValidAssistTarget(WorldObject const *target, SpellInfo const *bySpell=nullptr) const
Definition Object.cpp:2487
#define sWorld
Definition World.h:919
@ CONFIG_RESPAWN_DYNAMIC_ESCORTNPC
Definition World.h:194
static constexpr ObjectData creatureData[]
TC_GAME_API Player * GetPlayer(Map const *, ObjectGuid const &guid)
constexpr void NormalizeMapCoord(float &c)
void InitializeAI() override
float GetMaxPlayerDistance() const
void Start(bool isActiveAttacker=true, ObjectGuid playerGUID=ObjectGuid::Empty, Quest const *quest=nullptr, bool instantRespawn=false, bool canLoopPath=false)
void ReturnToLastPoint()
void AddWaypoint(uint32 id, float x, float y, float z, bool run)
uint32 _escortState
void EnterEvadeMode(EvadeReason why) override
void MoveInLineOfSight(Unit *who) override
ObjectGuid _playerGUID
bool HasEscortState(uint32 escortState)
Milliseconds _pauseTimer
virtual void UpdateEscortAI(uint32 diff)
void AddEscortState(uint32 escortState)
Quest const * _escortQuest
void RemoveEscortState(uint32 escortState)
void MovementInform(uint32, uint32) override
void LoadPath(uint32 pathId)
Player * GetPlayerForEscort()
void JustDied(Unit *) override
bool IsPlayerOrGroupInRange()
bool _activeAttacker
EscortAI(Creature *creature) noexcept
void SetEscortPaused(bool on)
WaypointPath _path
void UpdateAI(uint32 diff) override
bool _hasImmuneToNPCFlags
uint32 _playerCheckTimer
bool AssistPlayerInCombatAgainst(Unit *who)
bool _instantRespawn
bool _returnToStart
void SetCombatMovement(bool allowMovement)
bool IsCombatMovementAllowed() const
std::vector< WaypointNode > Nodes