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 "DB2Structure.h"
24#include "Errors.h"
25#include "Language.h"
26#include "Log.h"
27#include "Map.h"
28#include "MapReference.h"
29#include "MapUtils.h"
30#include "MotionMaster.h"
31#include "ObjectAccessor.h"
32#include "Player.h"
33#include "SmartEnum.h"
34#include "SpellHistory.h"
35#include "TemporarySummon.h"
36#include "Vehicle.h"
37#include <queue>
38
39std::unordered_map<std::pair<uint32, Difficulty>, AISpellInfoType> UnitAI::AISpellInfo;
41{
42 return Trinity::Containers::MapGetValuePtr(UnitAI::AISpellInfo, { spellId, difficulty });
43}
44
45CreatureAI::CreatureAI(Creature* creature, uint32 scriptId) noexcept
46 : UnitAI(creature), me(creature), _boundary(nullptr),
47 _negateBoundary(false), _scriptId(scriptId ? scriptId : creature->GetScriptId()), _isEngaged(false), _moveInLOSLocked(false)
48{
49 ASSERT(_scriptId, "A CreatureAI was initialized with an invalid scriptId!");
50}
51
55
56void CreatureAI::Talk(uint8 id, WorldObject const* whisperTarget /*= nullptr*/)
57{
58 sCreatureTextMgr->SendChat(me, id, whisperTarget);
59}
60
61// Disable CreatureAI when charmed
62void CreatureAI::OnCharmed(bool isNew)
63{
64 if (isNew && !me->IsCharmed() && !me->LastCharmerGUID.IsEmpty())
65 {
67 {
68 if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID))
69 me->EngageWithTarget(lastCharmer);
70 }
71
73
74 if (!me->IsInCombat())
76 }
77
78 UnitAI::OnCharmed(isNew);
79}
80
82{
83 Map* map = creature->GetMap();
84 if (!map->IsDungeon()) // use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
85 {
86 TC_LOG_ERROR("scripts.ai", "CreatureAI::DoZoneInCombat: call for map that isn't an instance ({})", creature->GetGUID().ToString());
87 return;
88 }
89
90 if (!map->HavePlayers())
91 return;
92
93 for (MapReference const& ref : map->GetPlayers())
94 {
95 if (Player* player = ref.GetSource())
96 {
97 if (!player->IsAlive() || !CombatManager::CanBeginCombat(creature, player))
98 continue;
99
100 creature->EngageWithTarget(player);
101
102 for (Unit* pet : player->m_Controlled)
103 creature->EngageWithTarget(pet);
104
105 if (Unit* vehicle = player->GetVehicleBase())
106 creature->EngageWithTarget(vehicle);
107 }
108 }
109}
110
111// scripts does not take care about MoveInLineOfSight loops
112// MoveInLineOfSight can be called inside another MoveInLineOfSight and cause stack overflow
114{
115 if (_moveInLOSLocked == true)
116 return;
117 _moveInLOSLocked = true;
119 _moveInLOSLocked = false;
120}
121
123{
124 if (me->IsEngaged())
125 return;
126
128 me->EngageWithTarget(who);
129}
130
132{
133 if (!target || !me->IsAlive())
134 return;
135
136 if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true))
137 me->EngageWithTarget(target);
138}
139
140// Distract creature, if player gets too close while stealthed/prowling
141void CreatureAI::TriggerAlert(Unit const* who) const
142{
143 // If there's no target, or target isn't a player do nothing
144 if (!who || who->GetTypeId() != TYPEID_PLAYER)
145 return;
146
147 // If this unit isn't an NPC, is already distracted, is fighting, is confused, stunned or fleeing, do nothing
149 return;
150
151 // Only alert for hostiles!
153 return;
154
155 // Send alert sound (if any) for this creature
157
158 // Face the unit (stealthed player) and set distracted state for 5 seconds
160}
161
162// adapted from logic in Spell:EffectSummonType before commit 8499434
163static bool ShouldFollowOnSpawn(SummonPropertiesEntry const* properties)
164{
165 // Summons without SummonProperties are generally scripted summons that don't belong to any owner
166 if (!properties)
167 return false;
168
169 switch (properties->Control)
170 {
172 return true;
176 return true;
177 switch (SummonTitle(properties->Title))
178 {
179 case SummonTitle::Pet:
184 return true;
185 default:
186 return false;
187 }
188 default:
189 return false;
190 }
191}
192
194{
195 if (!IsEngaged())
196 {
197 if (TempSummon* summon = me->ToTempSummon())
198 {
199 // Only apply this to specific types of summons
200 if (!summon->GetVehicle() && ShouldFollowOnSpawn(summon->m_Properties) && summon->CanFollowOwner())
201 {
202 if (Unit* owner = summon->GetCharmerOrOwner())
203 {
204 summon->GetMotionMaster()->Clear();
205 summon->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, summon->GetFollowAngle());
206 }
207 }
208 }
209 }
210}
211
213{
214 if (!IsEngaged() && !me->CanHaveThreatList())
215 EngagementStart(who);
216}
217
219{
220 if (!_EnterEvadeMode(why))
221 return;
222
223 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EnterEvadeMode: entering evade mode (why: {}) ({})", EnumUtils::ToConstant(why), me->GetGUID().ToString());
224
225 if (!me->GetVehicle()) // otherwise me will be in evade mode forever
226 {
227 if (Unit* owner = me->GetCharmerOrOwner())
228 {
231 }
232 else
233 {
234 // Required to prevent attacking creatures that are evading and cause them to reenter combat
235 // Does not apply to MoveFollow
238 }
239 }
240
241 Reset();
242}
243
245{
246 if (!IsEngaged())
247 return false;
248
249 if (!me->IsAlive())
250 {
252 return false;
253 }
254
256 {
257 if (Unit* victim = me->SelectVictim())
258 if (victim != me->GetVictim())
259 AttackStart(victim);
260
261 return me->GetVictim() != nullptr;
262 }
263 else if (!me->IsInCombat())
264 {
266 return false;
267 }
268 else if (me->GetVictim())
269 me->AttackStop();
270
271 return true;
272}
273
275{
276 if (_isEngaged)
277 {
278 TC_LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{}", me->GetDebugInfo());
279 return;
280 }
281 _isEngaged = true;
282
283 me->AtEngage(who);
284}
285
287{
288 if (!_isEngaged)
289 {
290 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{}", me->GetDebugInfo());
291 return;
292 }
293 _isEngaged = false;
294
295 me->AtDisengage();
296}
297
299{
300 if (me->IsInEvadeMode())
301 return false;
302
303 if (!me->IsAlive())
304 {
306 return false;
307 }
308
311
312 me->CombatStop(true);
314 me->SetTappedBy(nullptr);
315
318 me->SetCannotReachTarget(false);
323
324 return true;
325}
326
328{
329 if (victim && me->Attack(victim, true))
330 {
331 // Clear distracted state on attacking
333 {
336 }
337
339 }
340}
341
343{
344 return {};
345}
346
352int32 CreatureAI::VisualizeBoundary(Seconds duration, Unit* owner, bool fill) const
353{
354 typedef std::pair<int32, int32> coordinate;
355
356 if (!owner)
357 return -1;
358
359 if (!_boundary || _boundary->empty())
361
362 std::queue<coordinate> Q;
363 std::unordered_set<coordinate> alreadyChecked;
364 std::unordered_set<coordinate> outOfBounds;
365
366 Position startPosition = owner->GetPosition();
367 if (!IsInBoundary(&startPosition)) // fall back to creature position
368 {
369 startPosition = me->GetPosition();
370 if (!IsInBoundary(&startPosition)) // fall back to creature home position
371 {
372 startPosition = me->GetHomePosition();
373 if (!IsInBoundary(&startPosition))
375 }
376 }
377 float spawnZ = startPosition.GetPositionZ() + BOUNDARY_VISUALIZE_SPAWN_HEIGHT;
378
379 bool boundsWarning = false;
380 Q.push({ 0,0 });
381 while (!Q.empty())
382 {
383 coordinate front = Q.front();
384 bool hasOutOfBoundsNeighbor = false;
385 for (coordinate const& off : std::list<coordinate>{ {1, 0}, {0, 1}, {-1, 0}, {0, -1} })
386 {
387 coordinate next(front.first + off.first, front.second + off.second);
389 {
390 boundsWarning = true;
391 continue;
392 }
393 if (alreadyChecked.find(next) == alreadyChecked.end()) // never check a coordinate twice
394 {
395 Position nextPos(startPosition.GetPositionX() + next.first*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionY() + next.second*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionZ());
396 if (IsInBoundary(&nextPos))
397 Q.push(next);
398 else
399 {
400 outOfBounds.insert(next);
401 hasOutOfBoundsNeighbor = true;
402 }
403 alreadyChecked.insert(next);
404 }
405 else if (outOfBounds.find(next) != outOfBounds.end())
406 hasOutOfBoundsNeighbor = true;
407 }
408 if (fill || hasOutOfBoundsNeighbor)
409 {
410 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))
411 {
412 point->SetObjectScale(BOUNDARY_VISUALIZE_CREATURE_SCALE);
413 point->SetUnitFlag(UNIT_FLAG_STUNNED);
414 point->SetImmuneToAll(true);
415 if (!hasOutOfBoundsNeighbor)
416 point->SetUninteractible(true);
417 }
418 }
419
420 Q.pop();
421 }
422 return boundsWarning ? LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED : 0;
423}
424
425bool CreatureAI::IsInBoundary(Position const* who) const
426{
427 if (!_boundary)
428 return true;
429
430 if (!who)
431 who = me;
432
434}
435
436bool CreatureAI::IsInBounds(CreatureBoundary const& boundary, Position const* pos)
437{
438 for (AreaBoundary const* areaBoundary : boundary)
439 if (!areaBoundary->IsWithinBoundary(pos))
440 return false;
441
442 return true;
443}
444
445void CreatureAI::SetBoundary(CreatureBoundary const* boundary, bool negateBoundaries /*= false*/)
446{
447 _boundary = boundary;
448 _negateBoundary = negateBoundaries;
450}
451
453{
454 if (IsInBoundary())
455 return true;
456 else
457 {
459 return false;
460 }
461}
462
463Creature* CreatureAI::DoSummon(uint32 entry, Position const& pos, Milliseconds despawnTime, TempSummonType summonType)
464{
465 return me->SummonCreature(entry, pos, summonType, despawnTime);
466}
467
468Creature* CreatureAI::DoSummon(uint32 entry, WorldObject* obj, float radius, Milliseconds despawnTime, TempSummonType summonType)
469{
470 Position pos = obj->GetRandomNearPosition(radius);
471 return me->SummonCreature(entry, pos, summonType, despawnTime);
472}
473
474Creature* CreatureAI::DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius, Milliseconds despawnTime, TempSummonType summonType)
475{
476 Position pos = obj->GetRandomNearPosition(radius);
477 pos.m_positionZ += flightZ;
478 return me->SummonCreature(entry, pos, summonType, despawnTime);
479}
@ IN_MILLISECONDS
Definition Common.h:38
const float BOUNDARY_VISUALIZE_SPAWN_HEIGHT
const uint32 BOUNDARY_VISUALIZE_CREATURE
AISpellInfoType * GetAISpellInfo(uint32 spellId, Difficulty difficulty)
const int32 BOUNDARY_VISUALIZE_FAILSAFE_LIMIT
const int8 BOUNDARY_VISUALIZE_STEP_SIZE
const float BOUNDARY_VISUALIZE_CREATURE_SCALE
static bool ShouldFollowOnSpawn(SummonPropertiesEntry const *properties)
std::vector< AreaBoundary const * > CreatureBoundary
Definition CreatureAI.h:39
#define sCreatureTextMgr
Difficulty
Definition DBCEnums.h:932
uint8_t uint8
Definition Define.h:156
int8_t int8
Definition Define.h:152
int32_t int32
Definition Define.h:150
uint32_t uint32
Definition Define.h:154
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition Duration.h:28
#define ASSERT
Definition Errors.h:80
@ LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED
Definition Language.h:1248
@ LANG_CREATURE_MOVEMENT_NOT_BOUNDED
Definition Language.h:1247
@ LANG_CREATURE_NO_INTERIOR_POINT_FOUND
Definition Language.h:1246
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
TempSummonType
@ TEMPSUMMON_TIMED_DESPAWN
@ TYPEID_UNIT
Definition ObjectGuid.h:43
@ TYPEID_PLAYER
Definition ObjectGuid.h:44
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
#define PET_FOLLOW_DIST
Definition PetDefines.h:98
SummonTitle
@ AI_REACTION_ALERT
@ SUMMON_CATEGORY_PET
@ SUMMON_CATEGORY_ALLY
@ SUMMON_CATEGORY_WILD
EvadeReason
@ REACT_PASSIVE
@ REACT_AGGRESSIVE
@ UNIT_FLAG_STUNNED
@ UNIT_STATE_DISTRACTED
Definition Unit.h:273
@ UNIT_STATE_EVADE
Definition Unit.h:283
@ UNIT_STATE_CONFUSED
Definition Unit.h:272
@ UNIT_STATE_FLEEING
Definition Unit.h:268
@ UNIT_STATE_STUNNED
Definition Unit.h:264
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
TypeID GetTypeId() const
Definition BaseEntity.h:166
static bool CanBeginCombat(Unit const *a, Unit const *b)
int32 VisualizeBoundary(Seconds duration, Unit *owner=nullptr, bool fill=false) const
virtual void MoveInLineOfSight(Unit *)
void DoZoneInCombat()
Definition CreatureAI.h:169
CreatureAI(Creature *creature, uint32 scriptId={}) noexcept
bool IsEngaged() const
Definition CreatureAI.h:79
static bool IsInBounds(CreatureBoundary const &boundary, Position const *who)
void TriggerAlert(Unit const *who) const
CreatureBoundary const * _boundary
Definition CreatureAI.h:257
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:264
bool _negateBoundary
Definition CreatureAI.h:258
void JustEnteredCombat(Unit *) override
virtual void EnterEvadeMode(EvadeReason why=EvadeReason::Other)
void Talk(uint8 id, WorldObject const *whisperTarget=nullptr)
bool _EnterEvadeMode(EvadeReason why=EvadeReason::Other)
void OnCharmed(bool isNew) override
virtual void JustAppeared()
virtual bool CheckInRoom()
virtual Optional< QuestGiverStatus > GetDialogStatus(Player const *player)
== Gossip system ================================
bool IsInBoundary(Position const *who=nullptr) const
bool UpdateVictim()
virtual ~CreatureAI()
void SetBoundary(CreatureBoundary const *boundary, bool negativeBoundaries=false)
void AttackStart(Unit *victim) override
== Triggered Actions Requested ==================
void EngagementStart(Unit *who)
Creature *const me
Definition CreatureAI.h:63
Creature * DoSummon(uint32 entry, Position const &pos, Milliseconds despawnTime=30s, TempSummonType summonType=TEMPSUMMON_CORPSE_TIMED_DESPAWN)
void EngagementOver()
bool _moveInLOSLocked
Definition CreatureAI.h:265
void MoveInLineOfSight_Safe(Unit *who)
== Reactions At =================================
bool IsTapListNotClearedOnEvade() const
Definition Creature.h:307
void DoImmediateBoundaryCheck()
Definition Creature.h:365
void StartDefaultCombatMovement(Unit *victim, Optional< float > range={}, Optional< float > angle={})
bool IsAggroGracePeriodExpired()
Definition Creature.h:460
bool _IsTargetAcceptable(Unit const *target) const
bool IsCivilian() const
Definition Creature.h:126
void GetHomePosition(float &x, float &y, float &z, float &ori) const
Definition Creature.h:388
void SetLastDamagedTime(time_t val)
Definition Creature.h:442
bool HasReactState(ReactStates state) const
Definition Creature.h:176
void DoNotReacquireSpellFocusTarget()
bool IsEngaged() const override
void ResetPlayerDamageReq()
Definition Creature.h:415
void SetCannotReachTarget(bool cannotReach)
void SetTappedBy(Unit const *unit, bool withGroup=true)
void AtEngage(Unit *target) override
void AtDisengage() override
void SetTarget(ObjectGuid const &guid) override
bool IsStateRestoredOnEvade() const
Definition Creature.h:220
Unit * SelectVictim()
void SendAIReaction(AiReaction reactionType)
bool IsInEvadeMode() const
Definition Creature.h:217
std::string GetDebugInfo() const override
bool CanStartAttack(Unit const *u, bool force) const
static char const * ToConstant(Enum value)
Definition SmartEnum.h:121
Definition Map.h:225
bool IsDungeon() const
Definition Map.cpp:3267
bool HavePlayers() const
Definition Map.h:393
bool Instanceable() const
Definition Map.cpp:3262
PlayerList const & GetPlayers() const
Definition Map.h:403
void MoveFollow(Unit *target, float dist, Optional< ChaseAngle > angle={}, Optional< Milliseconds > duration={}, bool ignoreTargetWalk=false, MovementSlot slot=MOTION_SLOT_ACTIVE, Scripting::v2::ActionResultSetter< MovementStopReason > &&scriptResult={})
void MoveTargetedHome()
void MoveDistract(uint32 time, float orientation)
static ObjectGuid const Empty
Definition ObjectGuid.h:314
bool IsEmpty() const
Definition ObjectGuid.h:362
std::string ToString() const
void Clear()
Definition ObjectGuid.h:329
void ResetAllCooldowns()
virtual void Reset()
Definition UnitAI.h:64
static std::unordered_map< std::pair< uint32, Difficulty >, AISpellInfoType > AISpellInfo
Definition UnitAI.h:166
virtual void OnCharmed(bool isNew)
Definition UnitAI.cpp:49
Definition Unit.h:635
void ClearUnitState(uint32 f)
Definition Unit.h:744
bool IsCharmed() const
Definition Unit.h:1236
Vehicle * GetVehicle() const
Definition Unit.h:1784
void CombatStop(bool includingCast=false, bool mutualPvP=true, bool(*unitFilter)(Unit const *otherUnit)=nullptr)
Definition Unit.cpp:6012
bool CanHaveThreatList() const
====================== THREAT & COMBAT ====================
Definition Unit.h:1030
Unit * GetVehicleBase() const
Definition Unit.cpp:12111
MotionMaster * GetMotionMaster()
Definition Unit.h:1723
bool IsAlive() const
Definition Unit.h:1185
TempSummon * ToTempSummon()
Definition Unit.h:1828
void AddUnitState(uint32 f)
Definition Unit.h:742
Unit * GetCharmerOrOwner() const
Definition Unit.h:1221
bool Attack(Unit *victim, bool meleeAttack)
Definition Unit.cpp:5853
void EngageWithTarget(Unit *who)
Definition Unit.cpp:8494
virtual float GetFollowAngle() const
Definition Unit.h:1816
Unit * GetVictim() const
Definition Unit.h:726
bool HasUnitState(const uint32 f) const
Definition Unit.h:743
SpellHistory * GetSpellHistory()
Definition Unit.h:1498
ObjectGuid LastCharmerGUID
Definition Unit.h:1779
bool AttackStop()
Definition Unit.cpp:5965
void RemoveAurasOnEvade()
Definition Unit.cpp:4443
bool IsInCombat() const
Definition Unit.h:1058
Map * GetMap() const
Definition Object.h:411
bool IsHostileTo(WorldObject const *target) const
Definition Object.cpp:2181
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:1398
Position GetRandomNearPosition(float radius)
Definition Object.cpp:2769
TC_GAME_API Unit * GetUnit(WorldObject const &, ObjectGuid const &guid)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
constexpr float GetPositionX() const
Definition Position.h:87
float m_positionZ
Definition Position.h:66
constexpr float GetPositionY() const
Definition Position.h:88
float GetAbsoluteAngle(float x, float y) const
Definition Position.h:136
constexpr void GetPosition(float &x, float &y) const
Definition Position.h:92
constexpr float GetPositionZ() const
Definition Position.h:89
EnumFlag< SummonPropertiesFlags > GetFlags() const