TrinityCore
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 "Containers.h"
21#include "Creature.h"
22#include "CreatureAIImpl.h"
23#include "CreatureTextMgr.h"
24#include "DB2Structure.h"
25#include "Errors.h"
26#include "Language.h"
27#include "Log.h"
28#include "Map.h"
29#include "MapReference.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
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
53{
54}
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
127 if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who, false))
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;
177 return true;
178 switch (SummonTitle(properties->Title))
179 {
180 case SummonTitle::Pet:
185 return true;
186 default:
187 return false;
188 }
189 default:
190 return false;
191 }
192}
193
195{
196 if (!IsEngaged())
197 {
198 if (TempSummon* summon = me->ToTempSummon())
199 {
200 // Only apply this to specific types of summons
201 if (!summon->GetVehicle() && ShouldFollowOnSpawn(summon->m_Properties) && summon->CanFollowOwner())
202 {
203 if (Unit* owner = summon->GetCharmerOrOwner())
204 {
205 summon->GetMotionMaster()->Clear();
206 summon->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, summon->GetFollowAngle());
207 }
208 }
209 }
210 }
211}
212
214{
215 if (!IsEngaged() && !me->CanHaveThreatList())
216 EngagementStart(who);
217}
218
220{
221 if (!_EnterEvadeMode(why))
222 return;
223
224 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EnterEvadeMode: entering evade mode (why: {}) ({})", EnumUtils::ToConstant(why), me->GetGUID().ToString());
225
226 if (!me->GetVehicle()) // otherwise me will be in evade mode forever
227 {
228 if (Unit* owner = me->GetCharmerOrOwner())
229 {
232 }
233 else
234 {
235 // Required to prevent attacking creatures that are evading and cause them to reenter combat
236 // Does not apply to MoveFollow
239 }
240 }
241
242 Reset();
243}
244
246{
247 if (!IsEngaged())
248 return false;
249
250 if (!me->IsAlive())
251 {
253 return false;
254 }
255
257 {
258 if (Unit* victim = me->SelectVictim())
259 if (victim != me->GetVictim())
260 AttackStart(victim);
261
262 return me->GetVictim() != nullptr;
263 }
264 else if (!me->IsInCombat())
265 {
267 return false;
268 }
269 else if (me->GetVictim())
270 me->AttackStop();
271
272 return true;
273}
274
276{
277 if (_isEngaged)
278 {
279 TC_LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n{}", me->GetDebugInfo());
280 return;
281 }
282 _isEngaged = true;
283
284 me->AtEngage(who);
285}
286
288{
289 if (!_isEngaged)
290 {
291 TC_LOG_DEBUG("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n{}", me->GetDebugInfo());
292 return;
293 }
294 _isEngaged = false;
295
296 me->AtDisengage();
297}
298
300{
301 if (me->IsInEvadeMode())
302 return false;
303
304 if (!me->IsAlive())
305 {
307 return false;
308 }
309
311
312 me->CombatStop(true);
314 me->SetTappedBy(nullptr);
315
318 me->SetCannotReachTarget(false);
323
324 return true;
325}
326
328{
329 return {};
330}
331
337int32 CreatureAI::VisualizeBoundary(Seconds duration, Unit* owner, bool fill) const
338{
339 typedef std::pair<int32, int32> coordinate;
340
341 if (!owner)
342 return -1;
343
344 if (!_boundary || _boundary->empty())
346
347 std::queue<coordinate> Q;
348 std::unordered_set<coordinate> alreadyChecked;
349 std::unordered_set<coordinate> outOfBounds;
350
351 Position startPosition = owner->GetPosition();
352 if (!IsInBoundary(&startPosition)) // fall back to creature position
353 {
354 startPosition = me->GetPosition();
355 if (!IsInBoundary(&startPosition)) // fall back to creature home position
356 {
357 startPosition = me->GetHomePosition();
358 if (!IsInBoundary(&startPosition))
360 }
361 }
362 float spawnZ = startPosition.GetPositionZ() + BOUNDARY_VISUALIZE_SPAWN_HEIGHT;
363
364 bool boundsWarning = false;
365 Q.push({ 0,0 });
366 while (!Q.empty())
367 {
368 coordinate front = Q.front();
369 bool hasOutOfBoundsNeighbor = false;
370 for (coordinate const& off : std::list<coordinate>{ {1, 0}, {0, 1}, {-1, 0}, {0, -1} })
371 {
372 coordinate next(front.first + off.first, front.second + off.second);
374 {
375 boundsWarning = true;
376 continue;
377 }
378 if (alreadyChecked.find(next) == alreadyChecked.end()) // never check a coordinate twice
379 {
380 Position nextPos(startPosition.GetPositionX() + next.first*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionY() + next.second*BOUNDARY_VISUALIZE_STEP_SIZE, startPosition.GetPositionZ());
381 if (IsInBoundary(&nextPos))
382 Q.push(next);
383 else
384 {
385 outOfBounds.insert(next);
386 hasOutOfBoundsNeighbor = true;
387 }
388 alreadyChecked.insert(next);
389 }
390 else if (outOfBounds.find(next) != outOfBounds.end())
391 hasOutOfBoundsNeighbor = true;
392 }
393 if (fill || hasOutOfBoundsNeighbor)
394 {
395 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))
396 {
397 point->SetObjectScale(BOUNDARY_VISUALIZE_CREATURE_SCALE);
398 point->SetUnitFlag(UNIT_FLAG_STUNNED);
399 point->SetImmuneToAll(true);
400 if (!hasOutOfBoundsNeighbor)
401 point->SetUninteractible(true);
402 }
403 }
404
405 Q.pop();
406 }
407 return boundsWarning ? LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED : 0;
408}
409
410bool CreatureAI::IsInBoundary(Position const* who) const
411{
412 if (!_boundary)
413 return true;
414
415 if (!who)
416 who = me;
417
419}
420
421bool CreatureAI::IsInBounds(CreatureBoundary const& boundary, Position const* pos)
422{
423 for (AreaBoundary const* areaBoundary : boundary)
424 if (!areaBoundary->IsWithinBoundary(pos))
425 return false;
426
427 return true;
428}
429
430void CreatureAI::SetBoundary(CreatureBoundary const* boundary, bool negateBoundaries /*= false*/)
431{
432 _boundary = boundary;
433 _negateBoundary = negateBoundaries;
435}
436
438{
439 if (IsInBoundary())
440 return true;
441 else
442 {
444 return false;
445 }
446}
447
448Creature* CreatureAI::DoSummon(uint32 entry, Position const& pos, Milliseconds despawnTime, TempSummonType summonType)
449{
450 return me->SummonCreature(entry, pos, summonType, despawnTime);
451}
452
453Creature* CreatureAI::DoSummon(uint32 entry, WorldObject* obj, float radius, Milliseconds despawnTime, TempSummonType summonType)
454{
455 Position pos = obj->GetRandomNearPosition(radius);
456 return me->SummonCreature(entry, pos, summonType, despawnTime);
457}
458
459Creature* CreatureAI::DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius, Milliseconds despawnTime, TempSummonType summonType)
460{
461 Position pos = obj->GetRandomNearPosition(radius);
462 pos.m_positionZ += flightZ;
463 return me->SummonCreature(entry, pos, summonType, despawnTime);
464}
@ IN_MILLISECONDS
Definition: Common.h:35
const float BOUNDARY_VISUALIZE_SPAWN_HEIGHT
Definition: CreatureAI.cpp:336
const uint32 BOUNDARY_VISUALIZE_CREATURE
Definition: CreatureAI.cpp:332
AISpellInfoType * GetAISpellInfo(uint32 spellId, Difficulty difficulty)
Definition: CreatureAI.cpp:40
const int32 BOUNDARY_VISUALIZE_FAILSAFE_LIMIT
Definition: CreatureAI.cpp:335
const int8 BOUNDARY_VISUALIZE_STEP_SIZE
Definition: CreatureAI.cpp:334
const float BOUNDARY_VISUALIZE_CREATURE_SCALE
Definition: CreatureAI.cpp:333
static bool ShouldFollowOnSpawn(SummonPropertiesEntry const *properties)
Definition: CreatureAI.cpp:163
std::vector< AreaBoundary const * > CreatureBoundary
Definition: CreatureAI.h:37
#define sCreatureTextMgr
Difficulty
Definition: DBCEnums.h:873
uint8_t uint8
Definition: Define.h:145
int8_t int8
Definition: Define.h:141
int32_t int32
Definition: Define.h:139
uint32_t uint32
Definition: Define.h:143
std::chrono::seconds Seconds
Seconds shorthand typedef.
Definition: Duration.h:32
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition: Duration.h:29
#define ASSERT
Definition: Errors.h:68
@ LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED
Definition: Language.h:1235
@ LANG_CREATURE_MOVEMENT_NOT_BOUNDED
Definition: Language.h:1234
@ LANG_CREATURE_NO_INTERIOR_POINT_FOUND
Definition: Language.h:1233
#define TC_LOG_DEBUG(filterType__,...)
Definition: Log.h:156
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
TempSummonType
Definition: ObjectDefines.h:62
@ TEMPSUMMON_TIMED_DESPAWN
Definition: ObjectDefines.h:65
@ TYPEID_UNIT
Definition: ObjectGuid.h:40
@ TYPEID_PLAYER
Definition: ObjectGuid.h:41
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:25
#define PET_FOLLOW_DIST
Definition: PetDefines.h:97
SummonTitle
@ AI_REACTION_ALERT
@ SUMMON_CATEGORY_PET
@ SUMMON_CATEGORY_ALLY
@ SUMMON_CATEGORY_WILD
@ SUMMON_CATEGORY_UNK
EvadeReason
Definition: UnitAICommon.h:30
@ REACT_PASSIVE
Definition: UnitDefines.h:506
@ REACT_AGGRESSIVE
Definition: UnitDefines.h:508
@ UNIT_FLAG_STUNNED
Definition: UnitDefines.h:162
@ UNIT_STATE_DISTRACTED
Definition: Unit.h:262
@ UNIT_STATE_EVADE
Definition: Unit.h:272
@ UNIT_STATE_CONFUSED
Definition: Unit.h:261
@ UNIT_STATE_FLEEING
Definition: Unit.h:257
@ UNIT_STATE_STUNNED
Definition: Unit.h:253
static bool CanBeginCombat(Unit const *a, Unit const *b)
int32 VisualizeBoundary(Seconds duration, Unit *owner=nullptr, bool fill=false) const
Definition: CreatureAI.cpp:337
virtual void MoveInLineOfSight(Unit *)
Definition: CreatureAI.cpp:122
void DoZoneInCombat()
Definition: CreatureAI.h:161
bool IsEngaged() const
Definition: CreatureAI.h:77
static bool IsInBounds(CreatureBoundary const &boundary, Position const *who)
Definition: CreatureAI.cpp:421
void TriggerAlert(Unit const *who) const
Definition: CreatureAI.cpp:141
CreatureBoundary const * _boundary
Definition: CreatureAI.h:249
void OnOwnerCombatInteraction(Unit *target)
Definition: CreatureAI.cpp:131
Creature * DoSummonFlyer(uint32 entry, WorldObject *obj, float flightZ, float radius=5.0f, Milliseconds despawnTime=30s, TempSummonType summonType=TEMPSUMMON_CORPSE_TIMED_DESPAWN)
Definition: CreatureAI.cpp:459
bool _isEngaged
Definition: CreatureAI.h:256
bool _negateBoundary
Definition: CreatureAI.h:250
void JustEnteredCombat(Unit *) override
Definition: CreatureAI.cpp:213
virtual void EnterEvadeMode(EvadeReason why=EvadeReason::Other)
Definition: CreatureAI.cpp:219
void Talk(uint8 id, WorldObject const *whisperTarget=nullptr)
Definition: CreatureAI.cpp:56
uint32 const _scriptId
Definition: CreatureAI.h:255
bool _EnterEvadeMode(EvadeReason why=EvadeReason::Other)
Definition: CreatureAI.cpp:299
void OnCharmed(bool isNew) override
Definition: CreatureAI.cpp:62
CreatureAI(Creature *creature, uint32 scriptId={})
Definition: CreatureAI.cpp:45
virtual void JustAppeared()
Definition: CreatureAI.cpp:194
virtual bool CheckInRoom()
Definition: CreatureAI.cpp:437
virtual Optional< QuestGiverStatus > GetDialogStatus(Player const *player)
== Gossip system ================================
Definition: CreatureAI.cpp:327
bool IsInBoundary(Position const *who=nullptr) const
Definition: CreatureAI.cpp:410
bool UpdateVictim()
Definition: CreatureAI.cpp:245
virtual ~CreatureAI()
Definition: CreatureAI.cpp:52
void SetBoundary(CreatureBoundary const *boundary, bool negativeBoundaries=false)
Definition: CreatureAI.cpp:430
void EngagementStart(Unit *who)
Definition: CreatureAI.cpp:275
Creature *const me
Definition: CreatureAI.h:61
Creature * DoSummon(uint32 entry, Position const &pos, Milliseconds despawnTime=30s, TempSummonType summonType=TEMPSUMMON_CORPSE_TIMED_DESPAWN)
Definition: CreatureAI.cpp:448
void EngagementOver()
Definition: CreatureAI.cpp:287
bool _moveInLOSLocked
Definition: CreatureAI.h:257
void MoveInLineOfSight_Safe(Unit *who)
== Reactions At =================================
Definition: CreatureAI.cpp:113
bool IsTapListNotClearedOnEvade() const
Definition: Creature.h:255
void DoImmediateBoundaryCheck()
Definition: Creature.h:310
bool _IsTargetAcceptable(Unit const *target) const
Definition: Creature.cpp:2646
bool IsCivilian() const
Definition: Creature.h:115
void GetHomePosition(float &x, float &y, float &z, float &ori) const
Definition: Creature.h:335
void SetLastDamagedTime(time_t val)
Definition: Creature.h:389
bool HasReactState(ReactStates state) const
Definition: Creature.h:142
void DoNotReacquireSpellFocusTarget()
Definition: Creature.cpp:3514
bool IsEngaged() const override
Definition: Creature.cpp:3570
void ResetPlayerDamageReq()
Definition: Creature.h:362
void SetCannotReachTarget(bool cannotReach)
Definition: Creature.cpp:3280
void SetTappedBy(Unit const *unit, bool withGroup=true)
Definition: Creature.cpp:1342
void AtEngage(Unit *target) override
Definition: Creature.cpp:3577
void AtDisengage() override
Definition: Creature.cpp:3615
void SetTarget(ObjectGuid const &guid) override
Definition: Creature.cpp:3388
Unit * SelectVictim()
Definition: Creature.cpp:1214
void SendAIReaction(AiReaction reactionType)
Definition: Creature.cpp:2528
bool IsInEvadeMode() const
Definition: Creature.h:179
std::string GetDebugInfo() const override
Definition: Creature.cpp:3638
bool CanStartAttack(Unit const *u, bool force) const
Definition: Creature.cpp:2109
static char const * ToConstant(Enum value)
Definition: SmartEnum.h:120
Definition: Map.h:187
bool IsDungeon() const
Definition: Map.cpp:3205
bool HavePlayers() const
Definition: Map.h:353
PlayerList const & GetPlayers() const
Definition: Map.h:363
void MoveFollow(Unit *target, float dist, ChaseAngle angle, Optional< Milliseconds > duration={}, MovementSlot slot=MOTION_SLOT_ACTIVE)
void MoveTargetedHome()
void MoveDistract(uint32 time, float orientation)
static ObjectGuid const Empty
Definition: ObjectGuid.h:272
bool IsEmpty() const
Definition: ObjectGuid.h:317
std::string ToString() const
Definition: ObjectGuid.cpp:554
void Clear()
Definition: ObjectGuid.h:284
TypeID GetTypeId() const
Definition: Object.h:172
static ObjectGuid GetGUID(Object const *o)
Definition: Object.h:158
void ResetAllCooldowns()
Definition: UnitAI.h:50
virtual void Reset()
Definition: UnitAI.h:63
static std::unordered_map< std::pair< uint32, Difficulty >, AISpellInfoType > AISpellInfo
Definition: UnitAI.h:165
virtual void OnCharmed(bool isNew)
Definition: UnitAI.cpp:49
virtual void AttackStart(Unit *)
Definition: UnitAI.cpp:29
Definition: Unit.h:622
bool IsCharmed() const
Definition: Unit.h:1201
Vehicle * GetVehicle() const
Definition: Unit.h:1697
bool CanHaveThreatList() const
====================== THREAT & COMBAT ====================
Definition: Unit.h:1010
Unit * GetVehicleBase() const
Definition: Unit.cpp:11403
MotionMaster * GetMotionMaster()
Definition: Unit.h:1637
bool IsAlive() const
Definition: Unit.h:1150
TempSummon * ToTempSummon()
Definition: Unit.h:1743
ControlList m_Controlled
Definition: Unit.h:1197
void AddUnitState(uint32 f)
Definition: Unit.h:726
Unit * GetCharmerOrOwner() const
Definition: Unit.h:1186
void EngageWithTarget(Unit *who)
Definition: Unit.cpp:8050
virtual float GetFollowAngle() const
Definition: Unit.h:1731
Unit * GetVictim() const
Definition: Unit.h:710
bool HasUnitState(const uint32 f) const
Definition: Unit.h:727
SpellHistory * GetSpellHistory()
Definition: Unit.h:1443
ObjectGuid LastCharmerGUID
Definition: Unit.h:1693
void CombatStop(bool includingCast=false, bool mutualPvP=true)
Definition: Unit.cpp:5808
bool AttackStop()
Definition: Unit.cpp:5762
void RemoveAurasOnEvade()
Definition: Unit.cpp:4284
bool IsInCombat() const
Definition: Unit.h:1038
Map * GetMap() const
Definition: Object.h:604
bool IsHostileTo(WorldObject const *target) const
Definition: Object.cpp:2848
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:2013
Position GetRandomNearPosition(float radius)
Definition: Object.cpp:3411
TC_GAME_API Unit * GetUnit(WorldObject const &, ObjectGuid const &guid)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition: MapUtils.h:29
float m_positionZ
Definition: Position.h:55
float GetPositionZ() const
Definition: Position.h:78
float GetAbsoluteAngle(float x, float y) const
Definition: Position.h:125
float GetPositionX() const
Definition: Position.h:76
void GetPosition(float &x, float &y) const
Definition: Position.h:81
float GetPositionY() const
Definition: Position.h:77
EnumFlag< SummonPropertiesFlags > GetFlags() const