Files
CypherCore/Source/Game/AI/CoreAI/UnitAI.cs
T
Fabian a3dc7b3f48 Ported .Net Core commits:
hondacrx:
- Initial commit: Switch to .Net Core 2.0
- Fix build and removed not needed files
Fabi:
- Updated solution platforms.
- Changed folder structure.
- Change library target framework to netstandard2.0.
- Updated solution platforms again...
- Removed windows specific kernel32 function usage (Ctrl-C handler).
2017-10-26 17:23:44 +02:00

576 lines
21 KiB
C#

/*
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
*
* 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 3 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 <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Combat;
using Game.Entities;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
namespace Game.AI
{
public class UnitAI
{
public UnitAI(Unit _unit)
{
me = _unit;
}
public virtual void AttackStart(Unit victim)
{
if (victim != null && me.Attack(victim, true))
{
// Clear distracted state on attacking
if (me.HasUnitState(UnitState.Distracted))
{
me.ClearUnitState(UnitState.Distracted);
me.GetMotionMaster().Clear();
}
me.GetMotionMaster().MoveChase(victim);
}
}
public void AttackStartCaster(Unit victim, float dist)
{
if (victim != null && me.Attack(victim, false))
me.GetMotionMaster().MoveChase(victim, dist);
}
ThreatManager GetThreatManager()
{
return me.GetThreatManager();
}
void SortByDistanceTo(Unit reference, List<Unit> targets)
{
targets.Sort(new ObjectDistanceOrderPred(reference));
}
public void DoMeleeAttackIfReady()
{
if (me.HasUnitState(UnitState.Casting))
return;
Unit victim = me.GetVictim();
if (!me.IsWithinMeleeRange(victim))
return;
bool sparAttack = me.GetFactionTemplateEntry().ShouldSparAttack() && victim.GetFactionTemplateEntry().ShouldSparAttack();
//Make sure our attack is ready and we aren't currently casting before checking distance
if (me.isAttackReady())
{
if (sparAttack)
me.FakeAttackerStateUpdate(victim);
else
me.AttackerStateUpdate(victim);
me.resetAttackTimer();
}
if (me.haveOffhandWeapon() && me.isAttackReady(WeaponAttackType.OffAttack))
{
if (sparAttack)
me.FakeAttackerStateUpdate(victim, WeaponAttackType.OffAttack);
else
me.AttackerStateUpdate(victim, WeaponAttackType.OffAttack);
me.resetAttackTimer(WeaponAttackType.OffAttack);
}
}
public bool DoSpellAttackIfReady(uint spell)
{
if (me.HasUnitState(UnitState.Casting) || !me.isAttackReady())
return true;
var spellInfo = Global.SpellMgr.GetSpellInfo(spell);
if (spellInfo != null)
{
if (me.IsWithinCombatRange(me.GetVictim(), spellInfo.GetMaxRange(false)))
{
me.CastSpell(me.GetVictim(), spellInfo, TriggerCastFlags.None);
me.resetAttackTimer();
return true;
}
}
return false;
}
public Unit SelectTarget(SelectAggroTarget targetType, uint position = 0, float dist = 0.0f, bool playerOnly = false, int aura = 0)
{
return SelectTarget(targetType, position, new DefaultTargetSelector(me, dist, playerOnly, aura));
}
// Select the targets satifying the predicate.
public Unit SelectTarget(SelectAggroTarget targetType, uint position, ISelector selector)
{
var threatlist = GetThreatManager().getThreatList();
if (position >= threatlist.Count)
return null;
List<Unit> targetList = new List<Unit>();
Unit currentVictim = null;
var currentVictimReference = GetThreatManager().getCurrentVictim();
if (currentVictimReference != null)
{
currentVictim = currentVictimReference.getTarget();
// Current victim always goes first
if (currentVictim && selector.Check(currentVictim))
targetList.Add(currentVictim);
}
foreach (var hostileRef in threatlist)
{
if (currentVictim != null && hostileRef.getTarget() != currentVictim && selector.Check(hostileRef.getTarget()))
targetList.Add(hostileRef.getTarget());
else if (currentVictim == null && selector.Check(hostileRef.getTarget()))
targetList.Add(hostileRef.getTarget());
}
if (position >= targetList.Count)
return null;
if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest)
SortByDistanceTo(me, targetList);
switch (targetType)
{
case SelectAggroTarget.Nearest:
case SelectAggroTarget.TopAggro:
{
return targetList.First();
}
case SelectAggroTarget.Farthest:
case SelectAggroTarget.BottomAggro:
{
return targetList.Last();
}
case SelectAggroTarget.Random:
{
return targetList.SelectRandom();
}
default:
break;
}
return null;
}
public List<Unit> SelectTargetList(uint num, SelectAggroTarget targetType, float dist, bool playerOnly, int aura = 0)
{
return SelectTargetList(new DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType);
}
// Select the targets satifying the predicate.
// predicate shall extend std.unary_function<Unit*, bool>
public List<Unit> SelectTargetList(ISelector selector, uint maxTargets, SelectAggroTarget targetType)
{
var targetList = new List<Unit>();
var threatlist = GetThreatManager().getThreatList();
if (threatlist.Empty())
return targetList;
foreach (var hostileRef in threatlist)
if (selector.Check(hostileRef.getTarget()))
targetList.Add(hostileRef.getTarget());
if (targetList.Count < maxTargets)
return targetList;
if (targetType == SelectAggroTarget.Nearest || targetType == SelectAggroTarget.Farthest)
SortByDistanceTo(me, targetList);
if (targetType == SelectAggroTarget.Farthest || targetType == SelectAggroTarget.BottomAggro)
targetList.Reverse();
if (targetType == SelectAggroTarget.Random)
targetList = targetList.SelectRandom(maxTargets).ToList();
else
targetList.Resize(maxTargets);
return targetList;
}
public void DoCast(uint spellId)
{
Unit target = null;
switch (AISpellInfo[spellId].target)
{
default:
case AITarget.Self:
target = me;
break;
case AITarget.Victim:
target = me.GetVictim();
break;
case AITarget.Enemy:
{
var spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
if (spellInfo != null)
{
bool playerOnly = spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers);
target = SelectTarget(SelectAggroTarget.Random, 0, spellInfo.GetMaxRange(false), playerOnly);
}
break;
}
case AITarget.Ally:
case AITarget.Buff:
target = me;
break;
case AITarget.Debuff:
{
var spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
if (spellInfo != null)
{
bool playerOnly = spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers);
float range = spellInfo.GetMaxRange(false);
DefaultTargetSelector targetSelector = new DefaultTargetSelector(me, range, playerOnly, -(int)spellId);
if (!spellInfo.AuraInterruptFlags.HasAnyFlag(SpellAuraInterruptFlags.NotVictim)
&& targetSelector.Check(me.GetVictim()))
target = me.GetVictim();
else
target = SelectTarget(SelectAggroTarget.Random, 0, targetSelector);
}
break;
}
}
if (target != null)
me.CastSpell(target, spellId, false);
}
public void DoCast(Unit victim, uint spellId, bool triggered = false)
{
if (victim == null || (me.HasUnitState(UnitState.Casting) && !triggered))
return;
me.CastSpell(victim, spellId, triggered);
}
public void DoCastSelf(uint spellId, bool triggered = false) { DoCast(me, spellId, triggered); }
public void DoCastVictim(uint spellId, bool triggered = false)
{
if (me.GetVictim() == null || (me.HasUnitState(UnitState.Casting) && !triggered))
return;
me.CastSpell(me.GetVictim(), spellId, triggered);
}
public void DoCastAOE(uint spellId, bool triggered = false)
{
if (!triggered && me.HasUnitState(UnitState.Casting))
return;
me.CastSpell((Unit)null, spellId, triggered);
}
public static void FillAISpellInfo()
{
var spellStorage = Global.SpellMgr.GetSpellInfoStorage();
AISpellInfo = new AISpellInfoType[spellStorage.Keys.Max() + 1];
foreach (var spellInfo in spellStorage.Values)
{
AISpellInfoType AIInfo = AISpellInfo[spellInfo.Id];
if (spellInfo.HasAttribute(SpellAttr0.CastableWhileDead))
AIInfo.condition = AICondition.Die;
else if (spellInfo.IsPassive() || spellInfo.GetDuration() == -1)
AIInfo.condition = AICondition.Aggro;
else
AIInfo.condition = AICondition.Combat;
if (AIInfo.cooldown < spellInfo.RecoveryTime)
AIInfo.cooldown = spellInfo.RecoveryTime;
if (spellInfo.GetMaxRange(false) == 0)
{
if (AIInfo.target < AITarget.Self)
AIInfo.target = AITarget.Self;
}
else
{
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
{
if (effect == null)
continue;
var targetType = effect.TargetA.GetTarget();
if (targetType == Targets.UnitEnemy || targetType == Targets.DestEnemy)
{
if (AIInfo.target < AITarget.Victim)
AIInfo.target = AITarget.Victim;
}
else if (targetType == Targets.UnitDestAreaEnemy)
{
if (AIInfo.target < AITarget.Enemy)
AIInfo.target = AITarget.Enemy;
}
if (effect.Effect == SpellEffectName.ApplyAura)
{
if (targetType == Targets.UnitEnemy)
{
if (AIInfo.target < AITarget.Debuff)
AIInfo.target = AITarget.Debuff;
}
else if (spellInfo.IsPositive())
{
if (AIInfo.target < AITarget.Buff)
AIInfo.target = AITarget.Buff;
}
}
}
}
AIInfo.realCooldown = spellInfo.RecoveryTime + spellInfo.StartRecoveryTime;
AIInfo.maxRange = spellInfo.GetMaxRange(false) * 3 / 4;
}
}
public virtual bool CanAIAttack(Unit victim) { return true; }
public virtual void UpdateAI(uint diff) { }
public virtual void InitializeAI()
{
if (!me.IsDead())
Reset();
}
public virtual void Reset() { }
public virtual void OnCharmed(bool apply) { }
public virtual void DoAction(int param) { }
public virtual uint GetData(uint id = 0) { return 0; }
public virtual void SetData(uint id, uint value) { }
public virtual void SetGUID(ObjectGuid guid, int id = 0) { }
public virtual ObjectGuid GetGUID(int id = 0) { return ObjectGuid.Empty; }
public virtual void DamageDealt(Unit victim, ref uint damage, DamageEffectType damageType) { }
public virtual void DamageTaken(Unit attacker, ref uint damage) { }
public virtual void HealReceived(Unit by, uint addhealth) { }
public virtual void HealDone(Unit to, uint addhealth) { }
public virtual void SpellInterrupted(uint spellId, uint unTimeMs) {}
public virtual void sGossipHello(Player player) { }
public virtual void sGossipSelect(Player player, uint menuId, uint gossipListId) { }
public virtual void sGossipSelectCode(Player player, uint menuId, uint gossipListId, string code) { }
public virtual void sQuestAccept(Player player, Quest quest) { }
public virtual void sQuestSelect(Player player, Quest quest) { }
public virtual void sQuestComplete(Player player, Quest quest) { }
public virtual void sQuestReward(Player player, Quest quest, uint opt) { }
public virtual bool sOnDummyEffect(Unit caster, uint spellId, int effIndex) { return false; }
public virtual void sOnGameEvent(bool start, ushort eventId) { }
public static AISpellInfoType[] AISpellInfo;
protected Unit me { get; private set; }
}
public enum SelectAggroTarget
{
Random = 0, //Just selects a random target
TopAggro, //Selects targes from top aggro to bottom
BottomAggro, //Selects targets from bottom aggro to top
Nearest,
Farthest
}
public interface ISelector
{
bool Check(Unit target);
}
// default predicate function to select target based on distance, player and/or aura criteria
public class DefaultTargetSelector : ISelector
{
Unit me;
float m_dist;
bool m_playerOnly;
int m_aura;
// unit: the reference unit
// dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
// playerOnly: self explaining
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
public DefaultTargetSelector(Unit unit, float dist, bool playerOnly, int aura)
{
me = unit;
m_dist = dist;
m_playerOnly = playerOnly;
m_aura = aura;
}
public bool Check(Unit target)
{
if (me == null)
return false;
if (target == null)
return false;
if (m_playerOnly && !target.IsTypeId(TypeId.Player))
return false;
if (m_dist > 0.0f && !me.IsWithinCombatRange(target, m_dist))
return false;
if (m_dist < 0.0f && me.IsWithinCombatRange(target, -m_dist))
return false;
if (m_aura != 0)
{
if (m_aura > 0)
{
if (!target.HasAura((uint)m_aura))
return false;
}
else
{
if (target.HasAura((uint)-m_aura))
return false;
}
}
return true;
}
}
// Target selector for spell casts checking range, auras and attributes
// todo Add more checks from Spell.CheckCast
public class SpellTargetSelector : ISelector
{
public SpellTargetSelector(Unit caster, uint spellId)
{
_caster = caster;
_spellInfo = Global.SpellMgr.GetSpellInfo(spellId);
Contract.Assert(_spellInfo != null);
}
public bool Check(Unit target)
{
if (target == null)
return false;
if (_spellInfo.CheckTarget(_caster, target) != SpellCastResult.SpellCastOk)
return false;
// copypasta from Spell.CheckRange
float minRange = 0.0f;
float maxRange = 0.0f;
float rangeMod = 0.0f;
if (_spellInfo.RangeEntry != null)
{
if (_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee))
{
rangeMod = _caster.GetCombatReach() + 4.0f / 3.0f;
rangeMod += target.GetCombatReach();
rangeMod = Math.Max(rangeMod, SharedConst.NominalMeleeRange);
}
else
{
float meleeRange = 0.0f;
if (_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged))
{
meleeRange = _caster.GetCombatReach() + 4.0f / 3.0f;
meleeRange += target.GetCombatReach();
meleeRange = Math.Max(meleeRange, SharedConst.NominalMeleeRange);
}
minRange = _caster.GetSpellMinRangeForTarget(target, _spellInfo) + meleeRange;
maxRange = _caster.GetSpellMaxRangeForTarget(target, _spellInfo);
rangeMod = _caster.GetCombatReach();
rangeMod += target.GetCombatReach();
if (minRange > 0.0f && !_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged))
minRange += rangeMod;
}
if (_caster.isMoving() && target.isMoving() && !_caster.IsWalking() && !target.IsWalking() &&
(_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee) || target.IsTypeId(TypeId.Player)))
rangeMod += 8.0f / 3.0f;
}
maxRange += rangeMod;
minRange *= minRange;
maxRange *= maxRange;
if (target != _caster)
{
if (_caster.GetExactDistSq(target) > maxRange)
return false;
if (minRange > 0.0f && _caster.GetExactDistSq(target) < minRange)
return false;
}
return true;
}
Unit _caster;
SpellInfo _spellInfo;
}
// Very simple target selector, will just skip main target
// NOTE: When passing to UnitAI.SelectTarget remember to use 0 as position for random selection
// because tank will not be in the temporary list
public class NonTankTargetSelector : ISelector
{
public NonTankTargetSelector(Unit source, bool playerOnly = true)
{
_source = source;
_playerOnly = playerOnly;
}
public bool Check(Unit target)
{
if (target == null)
return false;
if (_playerOnly && !target.IsTypeId(TypeId.Player))
return false;
HostileReference currentVictim = _source.GetThreatManager().getCurrentVictim();
if (currentVictim != null)
return target.GetGUID() != currentVictim.getUnitGuid();
return target != _source.GetVictim();
}
Unit _source;
bool _playerOnly;
}
}