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