/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "UnitAI.h"
#include "Containers.h"
#include "Creature.h"
#include "CreatureAIImpl.h"
#include "Map.h"
#include "MotionMaster.h"
#include "Spell.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include
void UnitAI::AttackStart(Unit* victim)
{
if (victim && me->Attack(victim, true))
{
// Clear distracted state on attacking
if (me->HasUnitState(UNIT_STATE_DISTRACTED))
{
me->ClearUnitState(UNIT_STATE_DISTRACTED);
me->GetMotionMaster()->Clear();
}
me->GetMotionMaster()->MoveChase(victim);
}
}
void UnitAI::InitializeAI()
{
if (!me->isDead())
Reset();
}
void UnitAI::OnCharmed(bool isNew)
{
if (!isNew)
me->ScheduleAIChange();
}
void UnitAI::AttackStartCaster(Unit* victim, float dist)
{
if (victim && me->Attack(victim, false))
me->GetMotionMaster()->MoveChase(victim, dist);
}
bool UnitAI::DoSpellAttackIfReady(uint32 spellId)
{
if (me->HasUnitState(UNIT_STATE_CASTING) || !me->isAttackReady())
return true;
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
{
if (me->IsWithinCombatRange(me->GetVictim(), spellInfo->GetMaxRange(false)))
{
me->CastSpell(me->GetVictim(), spellId, me->GetMap()->GetDifficultyID());
me->resetAttackTimer();
return true;
}
}
return false;
}
Unit* UnitAI::SelectTarget(SelectTargetMethod targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura)
{
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
}
void UnitAI::SelectTargetList(std::list& targetList, uint32 num, SelectTargetMethod targetType, uint32 offset, float dist, bool playerOnly, bool withTank, int32 aura)
{
SelectTargetList(targetList, num, targetType, offset, DefaultTargetSelector(me, dist, playerOnly, withTank, aura));
}
SpellCastResult UnitAI::DoCast(uint32 spellId)
{
Unit* target = nullptr;
AITarget aiTargetType = AITARGET_SELF;
if (AISpellInfoType const* info = GetAISpellInfo(spellId, me->GetMap()->GetDifficultyID()))
aiTargetType = info->target;
switch (aiTargetType)
{
default:
case AITARGET_SELF:
target = me;
break;
case AITARGET_VICTIM:
target = me->GetVictim();
break;
case AITARGET_ENEMY:
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
{
DefaultTargetSelector targetSelectorInner(me, spellInfo->GetMaxRange(false), false, true, 0);
auto targetSelector = [&](Unit const* candidate) -> bool
{
if (!candidate->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && candidate->IsControlledByPlayer())
return false;
}
else if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
return targetSelectorInner(candidate);
};
target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
}
break;
}
case AITARGET_ALLY:
target = me;
break;
case AITARGET_BUFF:
target = me;
break;
case AITARGET_DEBUFF:
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, me->GetMap()->GetDifficultyID()))
{
float range = spellInfo->GetMaxRange(false);
DefaultTargetSelector targetSelectorInner(me, range, false, true, -(int32)spellId);
auto targetSelector = [&](Unit const* candidate) -> bool
{
if (!candidate->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && candidate->IsControlledByPlayer())
return false;
}
else if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
return targetSelectorInner(candidate);
};
if (!spellInfo->HasAuraInterruptFlag(SpellAuraInterruptFlags::NOT_VICTIM) && targetSelector(me->GetVictim()))
target = me->GetVictim();
else
target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
}
break;
}
}
if (target)
return me->CastSpell(target, spellId, false);
return SPELL_FAILED_BAD_TARGETS;
}
SpellCastResult UnitAI::DoCast(Unit* victim, uint32 spellId, CastSpellExtraArgs const& args)
{
if (me->HasUnitState(UNIT_STATE_CASTING) && !(args.TriggerFlags & TRIGGERED_IGNORE_CAST_IN_PROGRESS))
return SPELL_FAILED_SPELL_IN_PROGRESS;
return me->CastSpell(victim, spellId, args);
}
SpellCastResult UnitAI::DoCastVictim(uint32 spellId, CastSpellExtraArgs const& args)
{
if (Unit* victim = me->GetVictim())
return DoCast(victim, spellId, args);
return SPELL_FAILED_BAD_TARGETS;
}
#define UPDATE_TARGET(a) {if (AIInfo->targettarget=a;}
void UnitAI::FillAISpellInfo()
{
sSpellMgr->ForEachSpellInfo([](SpellInfo const* spellInfo)
{
AISpellInfoType* AIInfo = &AISpellInfo[{ spellInfo->Id, spellInfo->Difficulty }];
if (spellInfo->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))
AIInfo->condition = AICOND_DIE;
else if (spellInfo->IsPassive() || spellInfo->GetDuration() == -1)
AIInfo->condition = AICOND_AGGRO;
else
AIInfo->condition = AICOND_COMBAT;
if (AIInfo->cooldown.count() < int32(spellInfo->RecoveryTime))
AIInfo->cooldown = Milliseconds(spellInfo->RecoveryTime);
if (spellInfo->GetMaxRange(false))
{
for (SpellEffectInfo const& effect : spellInfo->GetEffects())
{
uint32 targetType = effect.TargetA.GetTarget();
if (targetType == TARGET_UNIT_TARGET_ENEMY
|| targetType == TARGET_DEST_TARGET_ENEMY)
UPDATE_TARGET(AITARGET_VICTIM)
else if (targetType == TARGET_UNIT_DEST_AREA_ENEMY)
UPDATE_TARGET(AITARGET_ENEMY)
if (effect.Effect == SPELL_EFFECT_APPLY_AURA)
{
if (targetType == TARGET_UNIT_TARGET_ENEMY)
UPDATE_TARGET(AITARGET_DEBUFF)
else if (spellInfo->IsPositive())
UPDATE_TARGET(AITARGET_BUFF)
}
}
}
AIInfo->realCooldown = Milliseconds(spellInfo->RecoveryTime + spellInfo->StartRecoveryTime);
AIInfo->maxRange = spellInfo->GetMaxRange(false) * 3 / 4;
AIInfo->Effects = 0;
AIInfo->Targets = 0;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
{
// Spell targets self.
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER)
AIInfo->Targets |= 1 << (SELECT_TARGET_SELF - 1);
// Spell targets a single enemy.
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_TARGET_ENEMY)
AIInfo->Targets |= 1 << (SELECT_TARGET_SINGLE_ENEMY - 1);
// Spell targets AoE at enemy.
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER ||
spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY)
AIInfo->Targets |= 1 << (SELECT_TARGET_AOE_ENEMY - 1);
// Spell targets an enemy.
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_TARGET_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER ||
spellEffectInfo.TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY)
AIInfo->Targets |= 1 << (SELECT_TARGET_ANY_ENEMY - 1);
// Spell targets a single friend (or self).
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ALLY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_PARTY)
AIInfo->Targets |= 1 << (SELECT_TARGET_SINGLE_FRIEND - 1);
// Spell targets AoE friends.
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER_AREA_PARTY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_LASTTARGET_AREA_PARTY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER)
AIInfo->Targets |= 1 << (SELECT_TARGET_AOE_FRIEND - 1);
// Spell targets any friend (or self).
if (spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_ALLY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_TARGET_PARTY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_CASTER_AREA_PARTY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_UNIT_LASTTARGET_AREA_PARTY ||
spellEffectInfo.TargetA.GetTarget() == TARGET_SRC_CASTER)
AIInfo->Targets |= 1 << (SELECT_TARGET_ANY_FRIEND - 1);
// Make sure that this spell includes a damage effect.
if (spellEffectInfo.Effect == SPELL_EFFECT_SCHOOL_DAMAGE ||
spellEffectInfo.Effect == SPELL_EFFECT_INSTAKILL ||
spellEffectInfo.Effect == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE ||
spellEffectInfo.Effect == SPELL_EFFECT_HEALTH_LEECH)
AIInfo->Effects |= 1 << (SELECT_EFFECT_DAMAGE - 1);
// Make sure that this spell includes a healing effect (or an apply aura with a periodic heal).
if (spellEffectInfo.Effect == SPELL_EFFECT_HEAL ||
spellEffectInfo.Effect == SPELL_EFFECT_HEAL_MAX_HEALTH ||
spellEffectInfo.Effect == SPELL_EFFECT_HEAL_MECHANICAL ||
(spellEffectInfo.Effect == SPELL_EFFECT_APPLY_AURA && spellEffectInfo.ApplyAuraName == 8))
AIInfo->Effects |= 1 << (SELECT_EFFECT_HEALING - 1);
// Make sure that this spell applies an aura.
if (spellEffectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
AIInfo->Effects |= 1 << (SELECT_EFFECT_AURA - 1);
}
});
}
Unit* UnitAI::FinalizeTargetSelection(std::list& targetList, SelectTargetMethod targetType)
{
// maybe nothing fulfills the predicate
if (targetList.empty())
return nullptr;
switch (targetType)
{
case SelectTargetMethod::MaxThreat:
case SelectTargetMethod::MinThreat:
case SelectTargetMethod::MaxDistance:
case SelectTargetMethod::MinDistance:
return targetList.front();
case SelectTargetMethod::Random:
return Trinity::Containers::SelectRandomContainerElement(targetList);
default:
break;
}
return nullptr;
}
bool UnitAI::PrepareTargetListSelection(std::list& targetList, SelectTargetMethod targetType, uint32 offset)
{
targetList.clear();
ThreatManager& mgr = me->GetThreatManager();
// shortcut: we're gonna ignore the first elements, and there's at most elements, so we ignore them all - nothing to do here
if (mgr.GetThreatListSize() <= offset)
return false;
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
{
for (ThreatReference const* ref : mgr.GetUnsortedThreatList())
{
if (ref->IsOffline())
continue;
targetList.push_back(ref->GetVictim());
}
}
else
{
Unit* currentVictim = mgr.GetCurrentVictim();
if (currentVictim)
targetList.push_back(currentVictim);
for (ThreatReference const* ref : mgr.GetSortedThreatList())
{
if (ref->IsOffline())
continue;
Unit* thisTarget = ref->GetVictim();
if (thisTarget != currentVictim)
targetList.push_back(thisTarget);
}
}
// shortcut: the list isn't gonna get any larger
if (targetList.size() <= offset)
{
targetList.clear();
return false;
}
// right now, list is unsorted for DISTANCE types - re-sort by SelectTargetMethod::MaxDistance
if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance)
targetList.sort(Trinity::ObjectDistanceOrderPred(me, targetType == SelectTargetMethod::MinDistance));
// now the list is MAX sorted, reverse for MIN types
if (targetType == SelectTargetMethod::MinThreat)
targetList.reverse();
// ignore the first elements
while (offset)
{
targetList.pop_front();
--offset;
}
return true;
}
void UnitAI::FinalizeTargetListSelection(std::list& targetList, uint32 num, SelectTargetMethod targetType)
{
if (targetList.size() <= num)
return;
if (targetType == SelectTargetMethod::Random)
Trinity::Containers::RandomResize(targetList, num);
else
targetList.resize(num);
}
std::string UnitAI::GetDebugInfo() const
{
std::stringstream sstr;
sstr << std::boolalpha
<< "Me: " << (me ? me->GetDebugInfo() : "NULL");
return sstr.str();
}