TrinityCore
Loading...
Searching...
No Matches
CreatureAI.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 "CreatureAI.h"
19#include "AreaBoundary.h"
20#include "Creature.h"
21#include "CreatureAIImpl.h"
22#include "CreatureTextMgr.h"
23#include "DBCStructure.h"
24#include "Language.h"
25#include "Log.h"
26#include "Map.h"
27#include "MapReference.h"
28#include "MotionMaster.h"
29#include "ObjectAccessor.h"
30#include "Player.h"
31#include "SpellMgr.h"
32#include "SpellHistory.h"
33#include "TemporarySummon.h"
34#include "Vehicle.h"
35#include "World.h"
36
39
40CreatureAI::CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), _isEngaged(false), _moveInLOSLocked(false)
41{
42}
43
47
48void CreatureAI::Talk(uint8 id, WorldObject const* whisperTarget /*= nullptr*/)
49{
50 sCreatureTextMgr->SendChat(me, id, whisperTarget);
51}
52
53// Disable CreatureAI when charmed
54void CreatureAI::OnCharmed(bool isNew)
55{
56 if (isNew && !me->IsCharmed() && !me->LastCharmerGUID.IsEmpty())
57 {
59 {
60 if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID))
61 me->EngageWithTarget(lastCharmer);
62 }
63
65
66 if (!me->IsInCombat())
68 }
69
70 UnitAI::OnCharmed(isNew);
71}
72
73void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/)
74{
75 if (!creature)
76 creature = me;
77
78 Map* map = creature->GetMap();
79 if (!map->IsDungeon()) // use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
80 {
81 TC_LOG_ERROR("scripts.ai", "CreatureAI::DoZoneInCombat: call for map that isn't an instance ({})", creature->GetGUID().ToString());
82 return;
83 }
84
85 if (!map->HavePlayers())
86 return;
87
88 for (MapReference const& ref : map->GetPlayers())
89 {
90 if (Player* player = ref.GetSource())
91 {
92 if (!player->IsAlive() || !CombatManager::CanBeginCombat(creature, player))
93 continue;
94
95 creature->EngageWithTarget(player);
96
97 for (Unit* pet : player->m_Controlled)
98 creature->EngageWithTarget(pet);
99
100 if (Unit* vehicle = player->GetVehicleBase())
101 creature->EngageWithTarget(vehicle);
102 }
103 }
104}
105
106// scripts does not take care about MoveInLineOfSight loops
107// MoveInLineOfSight can be called inside another MoveInLineOfSight and cause stack overflow
109{
110 if (_moveInLOSLocked == true)
111 return;
112 _moveInLOSLocked = true;
114 _moveInLOSLocked = false;
115}
116
118{
119 if (me->IsEngaged())
120 return;
121
122 if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who, false))
123 me->EngageWithTarget(who);
124}
125
127{
128 if (!target || !me->IsAlive())
129 return;
130
131 if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true))
132 me->EngageWithTarget(target);
133}
134
135// Distract creature, if player gets too close while stealthed/prowling
136void CreatureAI::TriggerAlert(Unit const* who) const
137{
138 // If there's no target, or target isn't a player do nothing
139 if (!who || who->GetTypeId() != TYPEID_PLAYER)
140 return;
141
142 // If this unit isn't an NPC, is already distracted, is fighting, is confused, stunned or fleeing, do nothing
144 return;
145
146 // Only alert for hostiles that can actually engage the target.
148 return;
149
150 // Send alert sound (if any) for this creature
152
153 // Face the unit (stealthed player) and set distracted state for 5 seconds
155}
156
157// adapted from logic in Spell:EffectSummonType before commit 8499434
158static bool ShouldFollowOnSpawn(SummonPropertiesEntry const* properties)
159{
160 // Summons without SummonProperties are generally scripted summons that don't belong to any owner
161 if (!properties)
162 return false;
163
164 switch (properties->Control)
165 {
167 return true;
171 if (properties->Flags & 512)
172 return true;
173 switch (properties->Title)
174 {
175 case SUMMON_TYPE_PET:
180 return true;
181 default:
182 return false;
183 }
184 default:
185 return false;
186 }
187}
188
190{
191 if (!IsEngaged())
192 {
193 if (TempSummon* summon = me->ToTempSummon())
194 {
195 // Only apply this to specific types of summons
196 if (!summon->GetVehicle() && ShouldFollowOnSpawn(summon->m_Properties) && summon->CanFollowOwner())
197 {
198 if (Unit* owner = summon->GetCharmerOrOwner())
199 {
200 summon->GetMotionMaster()->Clear();
201 summon->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, summon->GetFollowAngle());
202 }
203 }
204 }
205 }
206}
207
209{
210 if (!IsEngaged() && !me->CanHaveThreatList())
211 EngagementStart(who);
212}
213
215{
216 if (!_EnterEvadeMode(why))
217 return;
218
219 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EnterEvadeMode: entering evade mode (why: {}) ({})", why, me->GetGUID().ToString());
220
221 if (!me->GetVehicle()) // otherwise me will be in evade mode forever
222 {
223 if (Unit* owner = me->GetCharmerOrOwner())
224 {
227 }
228 else
229 {
230 // Required to prevent attacking creatures that are evading and cause them to reenter combat
231 // Does not apply to MoveFollow
234 }
235 }
236
237 Reset();
238}
239
241{
242 if (!IsEngaged())
243 return false;
244
245 if (!me->IsAlive())
246 {
248 return false;
249 }
250
252 {
253 if (Unit* victim = me->SelectVictim())
254 if (victim != me->GetVictim())
255 AttackStart(victim);
256
257 return me->GetVictim() != nullptr;
258 }
259 else if (!me->IsInCombat())
260 {
262 return false;
263 }
264 else if (me->GetVictim())
265 me->AttackStop();
266
267 return true;
268}
269
271{
272 if (_isEngaged)
273 {
274 TC_LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{}", me->GetDebugInfo());
275 return;
276 }
277 _isEngaged = true;
278
279 me->AtEngage(who);
280}
281
283{
284 if (!_isEngaged)
285 {
286 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{}", me->GetDebugInfo());
287 return;
288 }
289 _isEngaged = false;
290
291 me->AtDisengage();
292}
293
295{
296 if (me->IsInEvadeMode())
297 return false;
298
299 if (!me->IsAlive())
300 {
302 return false;
303 }
304
306 me->ClearComboPointHolders(); // Remove all combo points targeting this unit
307 me->CombatStop(true);
309 me->SetLootRecipient(nullptr);
312 me->SetCannotReachTarget(false);
317
318 return true;
319}
320
322static const float BOUNDARY_VISUALIZE_CREATURE_SCALE = 0.25f;
325static const float BOUNDARY_VISUALIZE_SPAWN_HEIGHT = 5.0f;
326int32 CreatureAI::VisualizeBoundary(Seconds duration, Unit* owner, bool fill) const
327{
328 typedef std::pair<int32, int32> coordinate;
329
330 if (!owner)
331 return -1;
332
333 if (!_boundary || _boundary->empty())
335
336 std::queue<coordinate> Q;
337 std::unordered_set<coordinate> alreadyChecked;
338 std::unordered_set<coordinate> outOfBounds;
339
340 Position startPosition = owner->GetPosition();
341 if (!IsInBoundary(&startPosition)) // fall back to creature position
342 {
343 startPosition = me->GetPosition();
344 if (!IsInBoundary(&startPosition)) // fall back to creature home position
345 {
346 startPosition = me->GetHomePosition();
347 if (!IsInBoundary(&startPosition))
349 }
350 }
351 float spawnZ = startPosition.GetPositionZ() + BOUNDARY_VISUALIZE_SPAWN_HEIGHT;
352
353 bool boundsWarning = false;
354 Q.push({ 0,0 });
355 while (!Q.empty())
356 {
357 coordinate front = Q.front();
358 bool hasOutOfBoundsNeighbor = false;
359 for (coordinate const& off : std::list<coordinate>{ {1, 0}, {0, 1}, {-1, 0}, {0, -1} })
360 {
361 coordinate next(front.first + off.first, front.second + off.second);
363 {
364 boundsWarning = true;
365 continue;
366 }
367 if (alreadyChecked.find(next) == alreadyChecked.end()) // never check a coordinate twice
368 {
369 Position nextPos(startPosition.GetPositionX() + next.first*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionY() + next.second*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionZ());
370 if (IsInBoundary(&nextPos))
371 Q.push(next);
372 else
373 {
374 outOfBounds.insert(next);
375 hasOutOfBoundsNeighbor = true;
376 }
377 alreadyChecked.insert(next);
378 }
379 else if (outOfBounds.find(next) != outOfBounds.end())
380 hasOutOfBoundsNeighbor = true;
381 }
382 if (fill || hasOutOfBoundsNeighbor)
383 {
384 if (TempSummon* point = owner->SummonCreature(BOUNDARY_VISUALIZE_CREATURE, Position(startPosition.GetPositionX() + front.first * BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionY() + front.second * BOUNDARY_VISUALIZE_STEP_SIZE, spawnZ), TEMPSUMMON_TIMED_DESPAWN, duration))
385 {
386 point->SetObjectScale(BOUNDARY_VISUALIZE_CREATURE_SCALE);
387 point->SetUnitFlag(UNIT_FLAG_STUNNED);
388 point->SetImmuneToAll(true);
389 if (!hasOutOfBoundsNeighbor)
390 point->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE);
391 }
392 }
393
394 Q.pop();
395 }
396 return boundsWarning ? LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED : 0;
397}
398
399bool CreatureAI::IsInBoundary(Position const* who) const
400{
401 if (!_boundary)
402 return true;
403
404 if (!who)
405 who = me;
406
408}
409
410bool CreatureAI::IsInBounds(CreatureBoundary const& boundary, Position const* pos)
411{
412 for (AreaBoundary const* areaBoundary : boundary)
413 if (!areaBoundary->IsWithinBoundary(pos))
414 return false;
415
416 return true;
417}
418
420{
421 if (IsInBoundary())
422 return true;
423 else
424 {
426 return false;
427 }
428}
429
430void CreatureAI::SetBoundary(CreatureBoundary const* boundary, bool negateBoundaries /*= false*/)
431{
432 _boundary = boundary;
433 _negateBoundary = negateBoundaries;
435}
436
437Creature* CreatureAI::DoSummon(uint32 entry, Position const& pos, Milliseconds despawnTime, TempSummonType summonType)
438{
439 return me->SummonCreature(entry, pos, summonType, despawnTime);
440}
441
442Creature* CreatureAI::DoSummon(uint32 entry, WorldObject* obj, float radius, Milliseconds despawnTime, TempSummonType summonType)
443{
444 Position pos = obj->GetRandomNearPosition(radius);
445 return me->SummonCreature(entry, pos, summonType, despawnTime);
446}
447
448Creature* CreatureAI::DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius, Milliseconds despawnTime, TempSummonType summonType)
449{
450 Position pos = obj->GetRandomNearPosition(radius);
451 pos.m_positionZ += flightZ;
452 return me->SummonCreature(entry, pos, summonType, despawnTime);
453}
@ IN_MILLISECONDS
Definition Common.h:35
AISpellInfoType * GetAISpellInfo(uint32 i)
static const float BOUNDARY_VISUALIZE_SPAWN_HEIGHT
static const uint32 BOUNDARY_VISUALIZE_CREATURE
static const int32 BOUNDARY_VISUALIZE_FAILSAFE_LIMIT
static const int8 BOUNDARY_VISUALIZE_STEP_SIZE
static const float BOUNDARY_VISUALIZE_CREATURE_SCALE
static bool ShouldFollowOnSpawn(SummonPropertiesEntry const *properties)
std::vector< AreaBoundary const * > CreatureBoundary
Definition CreatureAI.h:36
#define sCreatureTextMgr
uint8_t uint8
Definition Define.h:135
int8_t int8
Definition Define.h:131
int32_t int32
Definition Define.h:129
uint32_t uint32
Definition Define.h:133
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:27
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
@ LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED
Definition Language.h:1238
@ LANG_CREATURE_MOVEMENT_NOT_BOUNDED
Definition Language.h:1237
@ LANG_CREATURE_NO_INTERIOR_POINT_FOUND
Definition Language.h:1236
#define TC_LOG_DEBUG(filterType__,...)
Definition Log.h:156
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
TempSummonType
@ TEMPSUMMON_TIMED_DESPAWN
@ TYPEID_UNIT
Definition ObjectGuid.h:38
@ TYPEID_PLAYER
Definition ObjectGuid.h:39
#define PET_FOLLOW_DIST
Definition PetDefines.h:85
@ AI_REACTION_ALERT
@ SUMMON_TYPE_MINION
@ SUMMON_TYPE_GUARDIAN
@ SUMMON_TYPE_PET
@ SUMMON_TYPE_MINIPET
@ SUMMON_TYPE_GUARDIAN2
@ SUMMON_CATEGORY_PET
@ SUMMON_CATEGORY_ALLY
@ SUMMON_CATEGORY_WILD
@ SUMMON_CATEGORY_UNK
@ REACT_PASSIVE
@ REACT_AGGRESSIVE
@ UNIT_FLAG_STUNNED
@ UNIT_FLAG_UNINTERACTIBLE
@ UNIT_STATE_DISTRACTED
Definition Unit.h:232
@ UNIT_STATE_EVADE
Definition Unit.h:242
@ UNIT_STATE_CONFUSED
Definition Unit.h:231
@ UNIT_STATE_FLEEING
Definition Unit.h:227
@ UNIT_STATE_STUNNED
Definition Unit.h:223
static bool CanBeginCombat(Unit const *a, Unit const *b)
int32 VisualizeBoundary(Seconds duration, Unit *owner=nullptr, bool fill=false) const
CreatureAI(Creature *creature)
virtual void MoveInLineOfSight(Unit *)
bool IsEngaged() const
Definition CreatureAI.h:105
static bool IsInBounds(CreatureBoundary const &boundary, Position const *who)
void TriggerAlert(Unit const *who) const
CreatureBoundary const * _boundary
Definition CreatureAI.h:267
@ EVADE_REASON_BOUNDARY
Definition CreatureAI.h:95
@ EVADE_REASON_NO_HOSTILES
Definition CreatureAI.h:94
void OnOwnerCombatInteraction(Unit *target)
Creature * DoSummonFlyer(uint32 entry, WorldObject *obj, float flightZ, float radius=5.0f, Milliseconds despawnTime=30s, TempSummonType summonType=TEMPSUMMON_CORPSE_TIMED_DESPAWN)
bool _isEngaged
Definition CreatureAI.h:273
bool _EnterEvadeMode(EvadeReason why=EVADE_REASON_OTHER)
bool _negateBoundary
Definition CreatureAI.h:268
void JustEnteredCombat(Unit *) override
void DoZoneInCombat(Creature *creature=nullptr)
void Talk(uint8 id, WorldObject const *whisperTarget=nullptr)
void OnCharmed(bool isNew) override
virtual void JustAppeared()
virtual bool CheckInRoom()
bool IsInBoundary(Position const *who=nullptr) const
bool UpdateVictim()
virtual ~CreatureAI()
void SetBoundary(CreatureBoundary const *boundary, bool negativeBoundaries=false)
void EngagementStart(Unit *who)
Creature *const me
Definition CreatureAI.h:82
Creature * DoSummon(uint32 entry, Position const &pos, Milliseconds despawnTime=30s, TempSummonType summonType=TEMPSUMMON_CORPSE_TIMED_DESPAWN)
void EngagementOver()
bool _moveInLOSLocked
Definition CreatureAI.h:274
void MoveInLineOfSight_Safe(Unit *who)
== Reactions At =================================
virtual void EnterEvadeMode(EvadeReason why=EVADE_REASON_OTHER)
bool LoadCreaturesAddon()
void DoImmediateBoundaryCheck()
Definition Creature.h:267
bool _IsTargetAcceptable(Unit const *target) const
bool IsCivilian() const
Definition Creature.h:97
void SetLootRecipient(Unit *unit, bool withGroup=true)
void GetHomePosition(float &x, float &y, float &z, float &ori) const
Definition Creature.h:295
void SetLastDamagedTime(time_t val)
Definition Creature.h:350
bool HasReactState(ReactStates state) const
Definition Creature.h:121
void DoNotReacquireSpellFocusTarget()
bool IsEngaged() const override
void ResetPlayerDamageReq()
Definition Creature.h:323
void SetCannotReachTarget(bool cannotReach)
void SetTarget(ObjectGuid guid) override
void AtEngage(Unit *target) override
void AtDisengage() override
Unit * SelectVictim()
void SendAIReaction(AiReaction reactionType)
bool IsImmuneToPC() const
Definition Unit.h:1137
bool IsInEvadeMode() const
Definition Creature.h:146
std::string GetDebugInfo() const override
bool CanStartAttack(Unit const *u, bool force) const
Definition Map.h:281
bool IsDungeon() const
Definition Map.cpp:4236
bool HavePlayers() const
Definition Map.h:437
PlayerList const & GetPlayers() const
Definition Map.h:448
void MoveFollow(Unit *target, float dist, ChaseAngle angle, MovementSlot slot=MOTION_SLOT_ACTIVE)
void MoveTargetedHome()
void MoveDistract(uint32 time, float orientation)
static ObjectGuid const Empty
Definition ObjectGuid.h:140
bool IsEmpty() const
Definition ObjectGuid.h:172
std::string ToString() const
void Clear()
Definition ObjectGuid.h:150
TypeID GetTypeId() const
Definition Object.h:93
static ObjectGuid GetGUID(Object const *o)
Definition Object.h:78
void ResetAllCooldowns()
virtual void Reset()
Definition UnitAI.h:145
virtual void OnCharmed(bool isNew)
Definition UnitAI.cpp:42
static AISpellInfoType * AISpellInfo
Definition UnitAI.h:252
virtual void AttackStart(Unit *)
Definition UnitAI.cpp:30
Definition Unit.h:769
bool IsCharmed() const
Definition Unit.h:1280
Vehicle * GetVehicle() const
Definition Unit.h:1737
bool CanHaveThreatList() const
====================== THREAT & COMBAT ====================
Definition Unit.h:1122
Unit * GetVehicleBase() const
Definition Unit.cpp:11826
MotionMaster * GetMotionMaster()
Definition Unit.h:1667
bool IsAlive() const
Definition Unit.h:1234
TempSummon * ToTempSummon()
Definition Unit.h:1794
void AddUnitState(uint32 f)
Definition Unit.h:875
void ClearComboPointHolders()
Definition Unit.cpp:10528
Unit * GetCharmerOrOwner() const
Definition Unit.h:1265
void EngageWithTarget(Unit *who)
Definition Unit.cpp:8292
virtual float GetFollowAngle() const
Definition Unit.h:1782
Unit * GetVictim() const
Definition Unit.h:859
bool HasUnitState(const uint32 f) const
Definition Unit.h:876
SpellHistory * GetSpellHistory()
Definition Unit.h:1484
ObjectGuid LastCharmerGUID
Definition Unit.h:1732
void CombatStop(bool includingCast=false, bool mutualPvP=true)
Definition Unit.cpp:5691
bool AttackStop()
Definition Unit.cpp:5645
void RemoveAurasOnEvade()
Definition Unit.cpp:4218
bool IsInCombat() const
Definition Unit.h:1144
Map * GetMap() const
Definition Object.h:449
bool IsHostileTo(WorldObject const *target) const
Definition Object.cpp:2796
TempSummon * SummonCreature(uint32 entry, Position const &pos, TempSummonType despawnType=TEMPSUMMON_MANUAL_DESPAWN, Milliseconds despawnTime=0s, uint32 vehId=0, uint32 spellId=0, ObjectGuid privateObjectOwner=ObjectGuid::Empty)
Definition Object.cpp:1992
Position GetRandomNearPosition(float radius)
Definition Object.cpp:3264
TC_GAME_API Unit * GetUnit(WorldObject const &, ObjectGuid const &guid)
float m_positionZ
Definition Position.h:58
float GetPositionZ() const
Definition Position.h:81
float GetAbsoluteAngle(float x, float y) const
Definition Position.h:128
float GetPositionX() const
Definition Position.h:79
void GetPosition(float &x, float &y) const
Definition Position.h:84
float GetPositionY() const
Definition Position.h:80