Files
CypherCore/Source/Game/AI/CoreAI/UnitAICommon.cs
T
2024-03-19 17:21:32 -04:00

255 lines
7.8 KiB
C#

// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Constants;
using Game.Combat;
using Game.Entities;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Game.AI
{
// default predicate function to select target based on distance, player and/or aura criteria
public class DefaultTargetSelector : ICheck<Unit>
{
Unit _me;
float _dist;
bool _playerOnly;
Unit _exception;
int _aura;
/// <param name="unit">the reference unit</param>
/// <param name="dist">if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit</param>
/// <param name="playerOnly">self explaining</param>
/// <param name="withTank">allow current tank to be selected</param>
/// <param name="aura">if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura</param>
public DefaultTargetSelector(Unit unit, float dist, bool playerOnly, bool withTank, int aura)
{
_me = unit;
_dist = dist;
_playerOnly = playerOnly;
_exception = !withTank ? unit.GetThreatManager().GetLastVictim() : null;
_aura = aura;
}
public bool Invoke(Unit target)
{
if (_me == null)
return false;
if (target == null)
return false;
if (_exception != null && target == _exception)
return false;
if (_playerOnly && !target.IsTypeId(TypeId.Player))
return false;
if (_dist > 0.0f && !_me.IsWithinCombatRange(target, _dist))
return false;
if (_dist < 0.0f && _me.IsWithinCombatRange(target, -_dist))
return false;
if (_aura != 0)
{
if (_aura > 0)
{
if (!target.HasAura((uint)_aura))
return false;
}
else
{
if (target.HasAura((uint)-_aura))
return false;
}
}
return false;
}
}
// Target selector for spell casts checking range, auras and attributes
// todo Add more checks from Spell.CheckCast
public class SpellTargetSelector : ICheck<Unit>
{
Unit _caster;
SpellInfo _spellInfo;
public SpellTargetSelector(Unit caster, uint spellId)
{
_caster = caster;
_spellInfo = Global.SpellMgr.GetSpellInfo(spellId, caster.GetMap().GetDifficultyID());
Cypher.Assert(_spellInfo != null);
}
public bool Invoke(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.HasFlag(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.HasFlag(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.HasFlag(SpellRangeFlag.Ranged))
minRange += rangeMod;
}
if (_caster.IsMoving() && target.IsMoving() && !_caster.IsWalking() && !target.IsWalking() &&
(_spellInfo.RangeEntry.HasFlag(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;
}
}
// 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 : ICheck<Unit>
{
Unit _source;
bool _playerOnly;
public NonTankTargetSelector(Unit source, bool playerOnly = true)
{
_source = source;
_playerOnly = playerOnly;
}
public bool Invoke(Unit target)
{
if (target == null)
return false;
if (_playerOnly && !target.IsTypeId(TypeId.Player))
return false;
Unit currentVictim = _source.GetThreatManager().GetCurrentVictim();
if (currentVictim != null)
return target != currentVictim;
return target != _source.GetVictim();
}
}
// Simple selector for units using mana
class PowerUsersSelector : ICheck<Unit>
{
Unit _me;
PowerType _power;
float _dist;
bool _playerOnly;
public PowerUsersSelector(Unit unit, PowerType power, float dist, bool playerOnly)
{
_me = unit;
_power = power;
_dist = dist;
_playerOnly = playerOnly;
}
public bool Invoke(Unit target)
{
if (_me == null || target == null)
return false;
if (target.GetPowerType() != _power)
return false;
if (_playerOnly && target.GetTypeId() != TypeId.Player)
return false;
if (_dist > 0.0f && !_me.IsWithinCombatRange(target, _dist))
return false;
if (_dist < 0.0f && _me.IsWithinCombatRange(target, -_dist))
return false;
return true;
}
}
class FarthestTargetSelector : ICheck<Unit>
{
Unit _me;
float _dist;
bool _playerOnly;
bool _inLos;
public FarthestTargetSelector(Unit unit, float dist, bool playerOnly, bool inLos)
{
_me = unit;
_dist = dist;
_playerOnly = playerOnly;
_inLos = inLos;
}
public bool Invoke(Unit target)
{
if (_me == null || target == null)
return false;
if (_playerOnly && target.GetTypeId() != TypeId.Player)
return false;
if (_dist > 0.0f && !_me.IsWithinCombatRange(target, _dist))
return false;
if (_inLos && !_me.IsWithinLOSInMap(target))
return false;
return true;
}
}
}