TrinityCore
UnitAI.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 "UnitAI.h"
19#include "Containers.h"
20#include "Creature.h"
21#include "CreatureAIImpl.h"
22#include "Map.h"
23#include "MotionMaster.h"
24#include "Spell.h"
25#include "SpellInfo.h"
26#include "SpellMgr.h"
27#include <sstream>
28
30{
31 if (victim && me->Attack(victim, true))
32 {
33 // Clear distracted state on attacking
35 {
38 }
39 me->GetMotionMaster()->MoveChase(victim);
40 }
41}
42
44{
45 if (!me->isDead())
46 Reset();
47}
48
49void UnitAI::OnCharmed(bool isNew)
50{
51 if (!isNew)
53}
54
55void UnitAI::AttackStartCaster(Unit* victim, float dist)
56{
57 if (victim && me->Attack(victim, false))
58 me->GetMotionMaster()->MoveChase(victim, dist);
59}
60
62{
64 return true;
65
66 if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
67 {
68 if (me->IsWithinCombatRange(me->GetVictim(), spellInfo->GetMaxRange(false)))
69 {
70 me->CastSpell(me->GetVictim(), spellId, me->GetMap()->GetDifficultyID());
72 return true;
73 }
74 }
75
76 return false;
77}
78
79Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
80{
81 return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
82}
83
84void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType, uint32 offset, float dist, bool playerOnly, bool withTank, int32 aura)
85{
86 SelectTargetList(targetList, num, targetType, offset, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
87}
88
90{
91 Unit* target = nullptr;
92 AITarget aiTargetType = AITARGET_SELF;
93 if (AISpellInfoType const* info = GetAISpellInfo(spellId, me->GetMap()->GetDifficultyID()))
94 aiTargetType = info->target;
95
96 switch (aiTargetType)
97 {
98 default:
99 case AITARGET_SELF:
100 target = me;
101 break;
102 case AITARGET_VICTIM:
103 target = me->GetVictim();
104 break;
105 case AITARGET_ENEMY:
106 {
107 if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
108 {
109 DefaultTargetSelector targetSelectorInner(me, spellInfo->GetMaxRange(false), false, true, 0);
110 auto targetSelector = [&](Unit const* candidate) -> bool
111 {
112 if (!candidate->IsPlayer())
113 {
114 if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
115 return false;
116
117 if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && candidate->IsControlledByPlayer())
118 return false;
119 }
120 else if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
121 return false;
122
123 return targetSelectorInner(candidate);
124 };
125 target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
126 }
127 break;
128 }
129 case AITARGET_ALLY:
130 target = me;
131 break;
132 case AITARGET_BUFF:
133 target = me;
134 break;
135 case AITARGET_DEBUFF:
136 {
137 if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
138 {
139 float range = spellInfo->GetMaxRange(false);
140
141 DefaultTargetSelector targetSelectorInner(me, range, false, true, -(int32)spellId);
142 auto targetSelector = [&](Unit const* candidate) -> bool
143 {
144 if (!candidate->IsPlayer())
145 {
146 if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
147 return false;
148
149 if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && candidate->IsControlledByPlayer())
150 return false;
151 }
152 else if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
153 return false;
154
155 return targetSelectorInner(candidate);
156 };
157 if (!spellInfo->HasAuraInterruptFlag(SpellAuraInterruptFlags::NOT_VICTIM) && targetSelector(me->GetVictim()))
158 target = me->GetVictim();
159 else
160 target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
161 }
162 break;
163 }
164 }
165
166 if (target)
167 return me->CastSpell(target, spellId, false);
168
170}
171
173{
176
177 return me->CastSpell(victim, spellId, args);
178}
179
181{
182 if (Unit* victim = me->GetVictim())
183 return DoCast(victim, spellId, args);
184
186}
187
188#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
189
191{
192 sSpellMgr->ForEachSpellInfo([](SpellInfo const* spellInfo)
193 {
194 AISpellInfoType* AIInfo = &AISpellInfo[{ spellInfo->Id, spellInfo->Difficulty }];
195
197 AIInfo->condition = AICOND_DIE;
198 else if (spellInfo->IsPassive() || spellInfo->GetDuration() == -1)
199 AIInfo->condition = AICOND_AGGRO;
200 else
201 AIInfo->condition = AICOND_COMBAT;
202
203 if (AIInfo->cooldown.count() < int32(spellInfo->RecoveryTime))
204 AIInfo->cooldown = Milliseconds(spellInfo->RecoveryTime);
205
206 if (spellInfo->GetMaxRange(false))
207 {
208 for (SpellEffectInfo const& effect : spellInfo->GetEffects())
209 {
210 uint32 targetType = effect.TargetA.GetTarget();
211
212 if (targetType == TARGET_UNIT_TARGET_ENEMY
213 || targetType == TARGET_DEST_TARGET_ENEMY)
214 UPDATE_TARGET(AITARGET_VICTIM)
215 else if (targetType == TARGET_UNIT_DEST_AREA_ENEMY)
216 UPDATE_TARGET(AITARGET_ENEMY)
217
218 if (effect.Effect == SPELL_EFFECT_APPLY_AURA)
219 {
220 if (targetType == TARGET_UNIT_TARGET_ENEMY)
221 UPDATE_TARGET(AITARGET_DEBUFF)
222 else if (spellInfo->IsPositive())
223 UPDATE_TARGET(AITARGET_BUFF)
224 }
225 }
226 }
227 AIInfo->realCooldown = Milliseconds(spellInfo->RecoveryTime + spellInfo->StartRecoveryTime);
228 AIInfo->maxRange = spellInfo->GetMaxRange(false) * 3 / 4;
229
230 AIInfo->Effects = 0;
231 AIInfo->Targets = 0;
232
233 for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
234 {
235 // Spell targets self.
236 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER)
237 AIInfo->Targets |= 1 << (SELECT_TARGET_SELF - 1);
238
239 // Spell targets a single enemy.
240 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ENEMY ||
241 spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_TARGET_ENEMY)
242 AIInfo->Targets |= 1 << (SELECT_TARGET_SINGLE_ENEMY - 1);
243
244 // Spell targets AoE at enemy.
245 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY ||
246 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY ||
247 spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER ||
248 spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY)
249 AIInfo->Targets |= 1 << (SELECT_TARGET_AOE_ENEMY - 1);
250
251 // Spell targets an enemy.
252 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ENEMY ||
253 spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_TARGET_ENEMY ||
254 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY ||
255 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY ||
256 spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER ||
257 spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY)
258 AIInfo->Targets |= 1 << (SELECT_TARGET_ANY_ENEMY - 1);
259
260 // Spell targets a single friend (or self).
261 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER ||
262 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ALLY ||
263 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_PARTY)
264 AIInfo->Targets |= 1 << (SELECT_TARGET_SINGLE_FRIEND - 1);
265
266 // Spell targets AoE friends.
267 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER_AREA_PARTY ||
268 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_LASTTARGET_AREA_PARTY ||
269 spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER)
270 AIInfo->Targets |= 1 << (SELECT_TARGET_AOE_FRIEND - 1);
271
272 // Spell targets any friend (or self).
273 if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER ||
274 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ALLY ||
275 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_PARTY ||
276 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER_AREA_PARTY ||
277 spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_LASTTARGET_AREA_PARTY ||
278 spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER)
279 AIInfo->Targets |= 1 << (SELECT_TARGET_ANY_FRIEND - 1);
280
281 // Make sure that this spell includes a damage effect.
282 if (spellEffectInfo.Effect == SPELL_EFFECT_SCHOOL_DAMAGE ||
283 spellEffectInfo.Effect == SPELL_EFFECT_INSTAKILL ||
284 spellEffectInfo.Effect == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE ||
285 spellEffectInfo.Effect == SPELL_EFFECT_HEALTH_LEECH)
286 AIInfo->Effects |= 1 << (SELECT_EFFECT_DAMAGE - 1);
287
288 // Make sure that this spell includes a healing effect (or an apply aura with a periodic heal).
289 if (spellEffectInfo.Effect == SPELL_EFFECT_HEAL ||
290 spellEffectInfo.Effect == SPELL_EFFECT_HEAL_MAX_HEALTH ||
291 spellEffectInfo.Effect == SPELL_EFFECT_HEAL_MECHANICAL ||
292 (spellEffectInfo.Effect == SPELL_EFFECT_APPLY_AURA && spellEffectInfo.ApplyAuraName == 8))
293 AIInfo->Effects |= 1 << (SELECT_EFFECT_HEALING - 1);
294
295 // Make sure that this spell applies an aura.
296 if (spellEffectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
297 AIInfo->Effects |= 1 << (SELECT_EFFECT_AURA - 1);
298 }
299 });
300}
301
302Unit* UnitAI::FinalizeTargetSelection(std::list<Unit*>& targetList, SelectTargetMethod targetType)
303{
304 // maybe nothing fulfills the predicate
305 if (targetList.empty())
306 return nullptr;
307
308 switch (targetType)
309 {
314 return targetList.front();
317 default:
318 break;
319 }
320
321 return nullptr;
322}
323
324bool UnitAI::PrepareTargetListSelection(std::list<Unit*>& targetList, SelectTargetMethod targetType, uint32 offset)
325{
326 targetList.clear();
328 // shortcut: we're gonna ignore the first <offset> elements, and there's at most <offset> elements, so we ignore them all - nothing to do here
329 if (mgr.GetThreatListSize() <= offset)
330 return false;
331
332 if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
333 {
334 for (ThreatReference const* ref : mgr.GetUnsortedThreatList())
335 {
336 if (ref->IsOffline())
337 continue;
338
339 targetList.push_back(ref->GetVictim());
340 }
341 }
342 else
343 {
344 Unit* currentVictim = mgr.GetCurrentVictim();
345 if (currentVictim)
346 targetList.push_back(currentVictim);
347
348 for (ThreatReference const* ref : mgr.GetSortedThreatList())
349 {
350 if (ref->IsOffline())
351 continue;
352
353 Unit* thisTarget = ref->GetVictim();
354 if (thisTarget != currentVictim)
355 targetList.push_back(thisTarget);
356 }
357 }
358
359 // shortcut: the list isn't gonna get any larger
360 if (targetList.size() <= offset)
361 {
362 targetList.clear();
363 return false;
364 }
365
366 // right now, list is unsorted for DISTANCE types - re-sort by SelectTargetMethod::MaxDistance
367 if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
369
370 // now the list is MAX sorted, reverse for MIN types
371 if (targetType == SelectTargetMethod::MinThreat)
372 targetList.reverse();
373
374 // ignore the first <offset> elements
375 while (offset)
376 {
377 targetList.pop_front();
378 --offset;
379 }
380
381 return true;
382}
383
384void UnitAI::FinalizeTargetListSelection(std::list<Unit*>& targetList, uint32 num, SelectTargetMethod targetType)
385{
386 if (targetList.size() <= num)
387 return;
388
389 if (targetType == SelectTargetMethod::Random)
390 Trinity::Containers::RandomResize(targetList, num);
391 else
392 targetList.resize(num);
393}
394
395std::string UnitAI::GetDebugInfo() const
396{
397 std::stringstream sstr;
398 sstr << std::boolalpha
399 << "Me: " << (me ? me->GetDebugInfo() : "NULL");
400 return sstr.str();
401}
AITarget
@ AITARGET_ALLY
@ AITARGET_BUFF
@ AITARGET_SELF
@ AITARGET_VICTIM
@ AITARGET_ENEMY
@ AITARGET_DEBUFF
@ SELECT_EFFECT_AURA
@ SELECT_EFFECT_HEALING
@ SELECT_EFFECT_DAMAGE
@ SELECT_TARGET_ANY_FRIEND
@ SELECT_TARGET_AOE_FRIEND
@ SELECT_TARGET_ANY_ENEMY
@ SELECT_TARGET_SINGLE_FRIEND
@ SELECT_TARGET_SINGLE_ENEMY
@ SELECT_TARGET_SELF
@ SELECT_TARGET_AOE_ENEMY
@ AICOND_COMBAT
@ AICOND_AGGRO
@ AICOND_DIE
AISpellInfoType * GetAISpellInfo(uint32 spellId, Difficulty difficulty)
Definition: CreatureAI.cpp:40
int32_t int32
Definition: Define.h:139
uint32_t uint32
Definition: Define.h:143
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition: Duration.h:29
if(posix_memalign(&__mallocedMemory, __align, __size)) return NULL
@ SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC
@ SPELL_ATTR5_NOT_ON_PLAYER
@ TARGET_UNIT_TARGET_PARTY
@ TARGET_DEST_DYNOBJ_ENEMY
@ TARGET_UNIT_CASTER_AREA_PARTY
@ TARGET_UNIT_DEST_AREA_ENEMY
@ TARGET_UNIT_TARGET_ALLY
@ TARGET_UNIT_SRC_AREA_ENEMY
@ TARGET_DEST_TARGET_ENEMY
@ TARGET_UNIT_TARGET_ENEMY
@ TARGET_UNIT_LASTTARGET_AREA_PARTY
@ TARGET_UNIT_CASTER
@ TARGET_SRC_CASTER
@ SPELL_ATTR3_ONLY_ON_PLAYER
@ SPELL_EFFECT_HEALTH_LEECH
@ SPELL_EFFECT_HEAL
@ SPELL_EFFECT_HEAL_MAX_HEALTH
@ SPELL_EFFECT_HEAL_MECHANICAL
@ SPELL_EFFECT_ENVIRONMENTAL_DAMAGE
@ SPELL_EFFECT_SCHOOL_DAMAGE
@ SPELL_EFFECT_INSTAKILL
@ SPELL_EFFECT_APPLY_AURA
@ SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD
SpellCastResult
@ SPELL_FAILED_BAD_TARGETS
@ SPELL_FAILED_SPELL_IN_PROGRESS
@ TRIGGERED_IGNORE_CAST_IN_PROGRESS
Will not check if a current cast is in progress.
Definition: SpellDefines.h:252
#define sSpellMgr
Definition: SpellMgr.h:849
SelectTargetMethod
Definition: UnitAICommon.h:40
@ UNIT_STATE_DISTRACTED
Definition: Unit.h:262
@ UNIT_STATE_CASTING
Definition: Unit.h:265
Difficulty GetDifficultyID() const
Definition: Map.h:320
void MoveChase(Unit *target, Optional< ChaseRange > dist={}, Optional< ChaseAngle > angle={})
float GetMaxRange(bool positive=false, WorldObject *caster=nullptr, Spell *spell=nullptr) const
Definition: SpellInfo.cpp:3768
uint32 const Id
Definition: SpellInfo.h:325
uint32 RecoveryTime
Definition: SpellInfo.h:366
bool IsPassive() const
Definition: SpellInfo.cpp:1592
::Difficulty const Difficulty
Definition: SpellInfo.h:326
bool HasAttribute(SpellAttr0 attribute) const
Definition: SpellInfo.h:449
int32 GetDuration() const
Definition: SpellInfo.cpp:3791
std::vector< SpellEffectInfo > const & GetEffects() const
Definition: SpellInfo.h:576
uint32 StartRecoveryTime
Definition: SpellInfo.h:369
Unit * GetCurrentVictim()
Trinity::IteratorPair< ThreatListIterator, std::nullptr_t > GetUnsortedThreatList() const
Trinity::IteratorPair< ThreatListIterator, std::nullptr_t > GetSortedThreatList() const
size_t GetThreatListSize() const
bool DoSpellAttackIfReady(uint32 spellId)
Definition: UnitAI.cpp:61
void AttackStartCaster(Unit *victim, float dist)
Definition: UnitAI.cpp:55
static void FillAISpellInfo()
Definition: UnitAI.cpp:190
bool PrepareTargetListSelection(std::list< Unit * > &targetList, SelectTargetMethod targetType, uint32 offset)
Definition: UnitAI.cpp:324
virtual void Reset()
Definition: UnitAI.h:63
virtual void InitializeAI()
Definition: UnitAI.cpp:43
SpellCastResult DoCastVictim(uint32 spellId, CastSpellExtraArgs const &args={})
Definition: UnitAI.cpp:180
static std::unordered_map< std::pair< uint32, Difficulty >, AISpellInfoType > AISpellInfo
Definition: UnitAI.h:165
void SelectTargetList(std::list< Unit * > &targetList, uint32 num, SelectTargetMethod targetType, uint32 offset=0, float dist=0.0f, bool playerOnly=false, bool withTank=true, int32 aura=0)
Definition: UnitAI.cpp:84
virtual void OnCharmed(bool isNew)
Definition: UnitAI.cpp:49
virtual std::string GetDebugInfo() const
Definition: UnitAI.cpp:395
Unit * SelectTarget(SelectTargetMethod targetType, uint32 offset=0, float dist=0.0f, bool playerOnly=false, bool withTank=true, int32 aura=0)
Definition: UnitAI.cpp:79
Unit *const me
Definition: UnitAI.h:52
Unit * FinalizeTargetSelection(std::list< Unit * > &targetList, SelectTargetMethod targetType)
Definition: UnitAI.cpp:302
virtual void AttackStart(Unit *)
Definition: UnitAI.cpp:29
void FinalizeTargetListSelection(std::list< Unit * > &targetList, uint32 num, SelectTargetMethod targetType)
Definition: UnitAI.cpp:384
SpellCastResult DoCast(uint32 spellId)
Definition: UnitAI.cpp:89
Definition: Unit.h:622
void ClearUnitState(uint32 f)
Definition: Unit.h:728
ThreatManager & GetThreatManager()
Definition: Unit.h:1049
bool IsWithinCombatRange(Unit const *obj, float dist2compare) const
Definition: Unit.cpp:633
MotionMaster * GetMotionMaster()
Definition: Unit.h:1637
std::string GetDebugInfo() const override
Definition: Unit.cpp:13656
bool Attack(Unit *victim, bool meleeAttack)
Definition: Unit.cpp:5651
Unit * GetVictim() const
Definition: Unit.h:710
bool HasUnitState(const uint32 f) const
Definition: Unit.h:727
void ScheduleAIChange()
Definition: Unit.cpp:9502
bool isAttackReady(WeaponAttackType type=BASE_ATTACK) const
Definition: Unit.h:685
void resetAttackTimer(WeaponAttackType type=BASE_ATTACK)
Definition: Unit.cpp:628
bool isDead() const
Definition: Unit.h:1152
Map * GetMap() const
Definition: Object.h:604
SpellCastResult CastSpell(CastSpellTargetArg const &targets, uint32 spellId, CastSpellExtraArgs const &args={ })
Definition: Object.cpp:2884
auto SelectRandomContainerElement(C const &container) -> typename std::add_const< decltype(*std::begin(container))>::type &
Definition: Containers.h:109
void RandomResize(C &container, std::size_t requestedSize)
Definition: Containers.h:67
AICondition condition
Milliseconds cooldown
TriggerCastFlags TriggerFlags
Definition: SpellDefines.h:478