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).
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 Game.Entities;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class AreaTriggerAI
|
||||
{
|
||||
public AreaTriggerAI(AreaTrigger a)
|
||||
{
|
||||
at = a;
|
||||
}
|
||||
|
||||
// Called when the AreaTrigger has just been initialized, just before added to map
|
||||
public virtual void OnInitialize() { }
|
||||
|
||||
// Called when the AreaTrigger has just been created
|
||||
public virtual void OnCreate() { }
|
||||
|
||||
// Called on each AreaTrigger update
|
||||
public virtual void OnUpdate(uint diff) { }
|
||||
|
||||
// Called when the AreaTrigger reach splineIndex
|
||||
public virtual void OnSplineIndexReached(int splineIndex) { }
|
||||
|
||||
// Called when the AreaTrigger reach its destination
|
||||
public virtual void OnDestinationReached() { }
|
||||
|
||||
// Called when an unit enter the AreaTrigger
|
||||
public virtual void OnUnitEnter(Unit unit) { }
|
||||
|
||||
// Called when an unit exit the AreaTrigger, or when the AreaTrigger is removed
|
||||
public virtual void OnUnitExit(Unit unit) { }
|
||||
|
||||
// Called when the AreaTrigger is removed
|
||||
public virtual void OnRemove() { }
|
||||
|
||||
protected AreaTrigger at;
|
||||
}
|
||||
|
||||
class NullAreaTriggerAI : AreaTriggerAI
|
||||
{
|
||||
public NullAreaTriggerAI(AreaTrigger areaTrigger) : base(areaTrigger) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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.Entities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class CombatAI : CreatureAI
|
||||
{
|
||||
public CombatAI(Creature c) : base(c) { }
|
||||
|
||||
public override void InitializeAI()
|
||||
{
|
||||
for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i)
|
||||
if (me.m_spells[i] != 0 && Global.SpellMgr.GetSpellInfo(me.m_spells[i]) != null)
|
||||
spells.Add(me.m_spells[i]);
|
||||
|
||||
base.InitializeAI();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_events.Reset();
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
foreach (var id in spells)
|
||||
if (AISpellInfo[id].condition == AICondition.Die)
|
||||
me.CastSpell(killer, id, true);
|
||||
}
|
||||
|
||||
public override void EnterCombat(Unit victim)
|
||||
{
|
||||
foreach (var id in spells)
|
||||
{
|
||||
if (AISpellInfo[id].condition == AICondition.Aggro)
|
||||
me.CastSpell(victim, id, false);
|
||||
else if (AISpellInfo[id].condition == AICondition.Combat)
|
||||
_events.ScheduleEvent(id, AISpellInfo[id].cooldown + RandomHelper.Rand32() % AISpellInfo[id].cooldown);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
|
||||
uint spellId = _events.ExecuteEvent();
|
||||
if (spellId != 0)
|
||||
{
|
||||
DoCast(spellId);
|
||||
_events.ScheduleEvent(spellId, AISpellInfo[spellId].cooldown + RandomHelper.Rand32() % AISpellInfo[spellId].cooldown);
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public override void SpellInterrupted(uint spellId, uint unTimeMs)
|
||||
{
|
||||
_events.RescheduleEvent(spellId, unTimeMs);
|
||||
}
|
||||
|
||||
public List<uint> spells = new List<uint>();
|
||||
}
|
||||
|
||||
public class AggressorAI : CreatureAI
|
||||
{
|
||||
public AggressorAI(Creature c) : base(c) { }
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
|
||||
public class CasterAI : CombatAI
|
||||
{
|
||||
public CasterAI(Creature c)
|
||||
: base(c)
|
||||
{
|
||||
m_attackDist = SharedConst.MeleeRange;
|
||||
}
|
||||
|
||||
public override void InitializeAI()
|
||||
{
|
||||
base.InitializeAI();
|
||||
|
||||
m_attackDist = 30.0f;
|
||||
foreach (var id in spells)
|
||||
if (AISpellInfo[id].condition == AICondition.Combat && m_attackDist > AISpellInfo[id].maxRange)
|
||||
m_attackDist = AISpellInfo[id].maxRange;
|
||||
if (m_attackDist == 30.0f)
|
||||
m_attackDist = SharedConst.MeleeRange;
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit victim)
|
||||
{
|
||||
AttackStartCaster(victim, m_attackDist);
|
||||
}
|
||||
|
||||
public override void EnterCombat(Unit victim)
|
||||
{
|
||||
if (spells.Empty())
|
||||
return;
|
||||
|
||||
int spell = (int)(RandomHelper.Rand32() % spells.Count);
|
||||
uint count = 0;
|
||||
foreach (var id in spells)
|
||||
{
|
||||
|
||||
if (AISpellInfo[id].condition == AICondition.Aggro)
|
||||
me.CastSpell(victim, id, false);
|
||||
else if (AISpellInfo[id].condition == AICondition.Combat)
|
||||
{
|
||||
uint cooldown = AISpellInfo[id].realCooldown;
|
||||
if (count == spell)
|
||||
{
|
||||
DoCast(spells[spell]);
|
||||
cooldown += (uint)me.GetCurrentSpellCastTime(id);
|
||||
}
|
||||
_events.ScheduleEvent(id, cooldown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me.GetVictim().HasBreakableByDamageCrowdControlAura(me))
|
||||
{
|
||||
me.InterruptNonMeleeSpells(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
|
||||
uint spellId = _events.ExecuteEvent();
|
||||
if (spellId != 0)
|
||||
{
|
||||
DoCast(spellId);
|
||||
uint casttime = (uint)me.GetCurrentSpellCastTime(spellId);
|
||||
_events.ScheduleEvent(spellId, (casttime != 0 ? casttime : 500) + AISpellInfo[spellId].realCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
float m_attackDist;
|
||||
}
|
||||
|
||||
public class ArcherAI : CreatureAI
|
||||
{
|
||||
public ArcherAI(Creature c)
|
||||
: base(c)
|
||||
{
|
||||
if (me.m_spells[0] == 0)
|
||||
Log.outError(LogFilter.ScriptsAi, "ArcherAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry());
|
||||
|
||||
var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0]);
|
||||
m_minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0;
|
||||
|
||||
if (m_minRange == 0)
|
||||
m_minRange = SharedConst.MeleeRange;
|
||||
me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0;
|
||||
me.m_SightDistance = me.m_CombatDistance;
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit who)
|
||||
{
|
||||
if (who == null)
|
||||
return;
|
||||
|
||||
if (me.IsWithinCombatRange(who, m_minRange))
|
||||
{
|
||||
if (me.Attack(who, true) && !who.IsFlying())
|
||||
me.GetMotionMaster().MoveChase(who);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me.Attack(who, false) && !who.IsFlying())
|
||||
me.GetMotionMaster().MoveChase(who, me.m_CombatDistance);
|
||||
}
|
||||
|
||||
if (who.IsFlying())
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (!me.IsWithinCombatRange(me.GetVictim(), m_minRange))
|
||||
DoSpellAttackIfReady(me.m_spells[0]);
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
float m_minRange;
|
||||
}
|
||||
|
||||
public class TurretAI : CreatureAI
|
||||
{
|
||||
public TurretAI(Creature c)
|
||||
: base(c)
|
||||
{
|
||||
if (me.m_spells[0] == 0)
|
||||
Log.outError(LogFilter.Server, "TurretAI set for creature (entry = {0}) with spell1=0. AI will do nothing", me.GetEntry());
|
||||
|
||||
var spellInfo = Global.SpellMgr.GetSpellInfo(me.m_spells[0]);
|
||||
m_minRange = spellInfo != null ? spellInfo.GetMinRange(false) : 0;
|
||||
me.m_CombatDistance = spellInfo != null ? spellInfo.GetMaxRange(false) : 0;
|
||||
me.m_SightDistance = me.m_CombatDistance;
|
||||
}
|
||||
public override bool CanAIAttack(Unit victim)
|
||||
{
|
||||
/// todo use one function to replace it
|
||||
if (!me.IsWithinCombatRange(me.GetVictim(), me.m_CombatDistance)
|
||||
|| (m_minRange != 0 && me.IsWithinCombatRange(me.GetVictim(), m_minRange)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
public override void AttackStart(Unit victim)
|
||||
{
|
||||
if (victim != null)
|
||||
me.Attack(victim, false);
|
||||
}
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoSpellAttackIfReady(me.m_spells[0]);
|
||||
}
|
||||
|
||||
float m_minRange;
|
||||
}
|
||||
|
||||
public class VehicleAI : CreatureAI
|
||||
{
|
||||
const int VEHICLE_CONDITION_CHECK_TIME = 1000;
|
||||
const int VEHICLE_DISMISS_TIME = 5000;
|
||||
|
||||
public VehicleAI(Creature creature) : base(creature)
|
||||
{
|
||||
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
|
||||
LoadConditions();
|
||||
m_DoDismiss = false;
|
||||
m_DismissTimer = VEHICLE_DISMISS_TIME;
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
CheckConditions(diff);
|
||||
|
||||
if (m_DoDismiss)
|
||||
{
|
||||
if (m_DismissTimer < diff)
|
||||
{
|
||||
m_DoDismiss = false;
|
||||
me.DespawnOrUnsummon();
|
||||
}
|
||||
else
|
||||
m_DismissTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCharmed(bool apply)
|
||||
{
|
||||
if (!me.GetVehicleKit().IsVehicleInUse() && !apply && m_HasConditions)//was used and has conditions
|
||||
m_DoDismiss = true;//needs reset
|
||||
else if (apply)
|
||||
m_DoDismiss = false;//in use again
|
||||
|
||||
m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer
|
||||
}
|
||||
|
||||
void LoadConditions()
|
||||
{
|
||||
m_HasConditions = Global.ConditionMgr.HasConditionsForNotGroupedEntry(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry());
|
||||
}
|
||||
|
||||
void CheckConditions(uint diff)
|
||||
{
|
||||
if (m_ConditionsTimer < diff)
|
||||
{
|
||||
if (m_HasConditions)
|
||||
{
|
||||
Vehicle vehicleKit = me.GetVehicleKit();
|
||||
if (vehicleKit)
|
||||
{
|
||||
foreach (var pair in vehicleKit.Seats)
|
||||
{
|
||||
Unit passenger = Global.ObjAccessor.GetUnit(me, pair.Value.Passenger.Guid);
|
||||
if (passenger)
|
||||
{
|
||||
Player player = passenger.ToPlayer();
|
||||
if (player)
|
||||
{
|
||||
if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.CreatureTemplateVehicle, me.GetEntry(), player, me))
|
||||
{
|
||||
player.ExitVehicle();
|
||||
return;//check other pessanger in next tick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
|
||||
}
|
||||
else
|
||||
m_ConditionsTimer -= diff;
|
||||
}
|
||||
|
||||
bool m_HasConditions;
|
||||
uint m_ConditionsTimer;
|
||||
bool m_DoDismiss;
|
||||
uint m_DismissTimer;
|
||||
}
|
||||
|
||||
public class ReactorAI : CreatureAI
|
||||
{
|
||||
public ReactorAI(Creature c) : base(c) { }
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* 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 Framework.Dynamic;
|
||||
using Game.Entities;
|
||||
using Game.Maps;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class CreatureAI : UnitAI
|
||||
{
|
||||
public CreatureAI(Creature _creature) : base(_creature)
|
||||
{
|
||||
me = _creature;
|
||||
MoveInLineOfSight_locked = false;
|
||||
}
|
||||
|
||||
public override void OnCharmed(bool apply)
|
||||
{
|
||||
if (apply)
|
||||
{
|
||||
me.NeedChangeAI = true;
|
||||
me.IsAIEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Talk(uint id, WorldObject whisperTarget = null)
|
||||
{
|
||||
Global.CreatureTextMgr.SendChat(me, (byte)id, whisperTarget);
|
||||
}
|
||||
|
||||
public void DoZoneInCombat(Creature creature = null, float maxRangeToNearestTarget = 250.0f)
|
||||
{
|
||||
if (!creature)
|
||||
creature = me;
|
||||
|
||||
if (!creature.CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Map map = creature.GetMap();
|
||||
if (!map.IsDungeon()) //use IsDungeon instead of Instanceable, in case Battlegrounds will be instantiated
|
||||
{
|
||||
Log.outError(LogFilter.Server, "DoZoneInCombat call for map that isn't an instance (creature entry = {0})", creature.IsTypeId(TypeId.Unit) ? creature.ToCreature().GetEntry() : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
|
||||
{
|
||||
Unit nearTarget = creature.SelectNearestTarget(maxRangeToNearestTarget);
|
||||
if (nearTarget != null)
|
||||
creature.GetAI().AttackStart(nearTarget);
|
||||
else if (creature.IsSummon())
|
||||
{
|
||||
Unit summoner = creature.ToTempSummon().GetSummoner();
|
||||
if (summoner != null)
|
||||
{
|
||||
Unit target = summoner.getAttackerForHelper();
|
||||
if (target == null && summoner.CanHaveThreatList() && !summoner.GetThreatManager().isThreatListEmpty())
|
||||
target = summoner.GetThreatManager().getHostilTarget();
|
||||
if (target != null && (creature.IsFriendlyTo(summoner) || creature.IsHostileTo(target)))
|
||||
creature.GetAI().AttackStart(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intended duplicated check, the code above this should select a victim
|
||||
// If it can't find a suitable attack target then we should error out.
|
||||
if (!creature.HasReactState(ReactStates.Passive) && creature.GetVictim() == null)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "DoZoneInCombat called for creature that has empty threat list (creature entry = {0})", creature.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
var playerList = map.GetPlayers();
|
||||
if (playerList.Empty())
|
||||
return;
|
||||
|
||||
foreach (var player in playerList)
|
||||
{
|
||||
if (player.IsGameMaster())
|
||||
continue;
|
||||
|
||||
if (player.IsAlive())
|
||||
{
|
||||
creature.SetInCombatWith(player);
|
||||
player.SetInCombatWith(creature);
|
||||
creature.AddThreat(player, 0.0f);
|
||||
}
|
||||
|
||||
/* Causes certain things to never leave the threat list (Priest Lightwell, etc):
|
||||
foreach (var unit in player.m_Controlled)
|
||||
{
|
||||
me.SetInCombatWith(unit);
|
||||
unit.SetInCombatWith(me);
|
||||
me.AddThreat(unit, 0.0f);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void MoveInLineOfSight_Safe(Unit who)
|
||||
{
|
||||
if (MoveInLineOfSight_locked)
|
||||
return;
|
||||
MoveInLineOfSight_locked = true;
|
||||
MoveInLineOfSight(who);
|
||||
MoveInLineOfSight_locked = false;
|
||||
}
|
||||
|
||||
public virtual void MoveInLineOfSight(Unit who)
|
||||
{
|
||||
if (me.GetVictim() != null)
|
||||
return;
|
||||
|
||||
if (me.GetCreatureType() == CreatureType.NonCombatPet)
|
||||
return;
|
||||
|
||||
if (me.HasReactState(ReactStates.Aggressive) && me.CanStartAttack(who, false))
|
||||
AttackStart(who);
|
||||
}
|
||||
|
||||
// Distract creature, if player gets too close while stealthed/prowling
|
||||
public void TriggerAlert(Unit who)
|
||||
{
|
||||
// If there's no target, or target isn't a player do nothing
|
||||
if (!who || !who.IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
// If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing
|
||||
if (!me.IsTypeId(TypeId.Unit) || me.IsInCombat() || me.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing | UnitState.Distracted))
|
||||
return;
|
||||
|
||||
// Only alert for hostiles!
|
||||
if (me.IsCivilian() || me.HasReactState(ReactStates.Passive) || !me.IsHostileTo(who) || !me._IsTargetAcceptable(who))
|
||||
return;
|
||||
|
||||
// Send alert sound (if any) for this creature
|
||||
me.SendAIReaction(AiReaction.Alert);
|
||||
|
||||
// Face the unit (stealthed player) and set distracted state for 5 seconds
|
||||
me.SetFacingTo(me.GetAngle(who.GetPositionX(), who.GetPositionY()), true);
|
||||
me.StopMoving();
|
||||
me.GetMotionMaster().MoveDistract(5 * Time.InMilliseconds);
|
||||
}
|
||||
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
public virtual void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
|
||||
{
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
Log.outDebug( LogFilter.Unit, "Creature {0} enters evade mode.", me.GetEntry());
|
||||
|
||||
if (me.GetVehicle() == null) // otherwise me will be in evade mode forever
|
||||
{
|
||||
Unit owner = me.GetCharmerOrOwner();
|
||||
if (owner != null)
|
||||
{
|
||||
me.GetMotionMaster().Clear(false);
|
||||
me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Required to prevent attacking creatures that are evading and cause them to reenter combat
|
||||
// Does not apply to MoveFollow
|
||||
me.AddUnitState(UnitState.Evade);
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
}
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
if (me.IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons!
|
||||
me.GetVehicleKit().Reset(true);
|
||||
}
|
||||
|
||||
void SetGazeOn(Unit target)
|
||||
{
|
||||
if (me.IsValidAttackTarget(target))
|
||||
{
|
||||
if (!me.IsFocusing(null, true))
|
||||
AttackStart(target);
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateVictimWithGaze()
|
||||
{
|
||||
if (!me.IsInCombat())
|
||||
return false;
|
||||
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
{
|
||||
if (me.GetVictim() != null)
|
||||
return true;
|
||||
else
|
||||
me.SetReactState(ReactStates.Aggressive);
|
||||
}
|
||||
|
||||
Unit victim = me.SelectVictim();
|
||||
if (victim != null)
|
||||
{
|
||||
if (!me.IsFocusing(null, true))
|
||||
AttackStart(victim);
|
||||
}
|
||||
|
||||
return me.GetVictim() != null;
|
||||
}
|
||||
|
||||
public bool UpdateVictim()
|
||||
{
|
||||
if (!me.IsInCombat())
|
||||
return false;
|
||||
|
||||
if (!me.HasReactState(ReactStates.Passive))
|
||||
{
|
||||
Unit victim = me.SelectVictim();
|
||||
if (victim != null)
|
||||
if (!me.IsFocusing(null, true))
|
||||
AttackStart(victim);
|
||||
|
||||
return me.GetVictim() != null;
|
||||
}
|
||||
else if (me.GetThreatManager().isThreatListEmpty())
|
||||
{
|
||||
EnterEvadeMode(EvadeReason.NoHostiles);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool _EnterEvadeMode(EvadeReason why = EvadeReason.Other)
|
||||
{
|
||||
if (!me.IsAlive())
|
||||
return false;
|
||||
|
||||
me.RemoveAurasOnEvade();
|
||||
|
||||
// sometimes bosses stuck in combat?
|
||||
me.DeleteThreatList();
|
||||
me.CombatStop(true);
|
||||
me.SetLootRecipient(null);
|
||||
me.ResetPlayerDamageReq();
|
||||
me.SetLastDamagedTime(0);
|
||||
me.SetCannotReachTarget(false);
|
||||
|
||||
if (me.IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public CypherStrings VisualizeBoundary(int duration, Unit owner = null, bool fill = false)
|
||||
{
|
||||
if (!owner)
|
||||
return 0;
|
||||
|
||||
if (_boundary.Empty())
|
||||
return CypherStrings.CreatureMovementNotBounded;
|
||||
|
||||
List<KeyValuePair<int, int>> Q = new List<KeyValuePair<int, int>>();
|
||||
List<KeyValuePair<int, int>> alreadyChecked = new List<KeyValuePair<int, int>>();
|
||||
List<KeyValuePair<int, int>> outOfBounds = new List<KeyValuePair<int, int>>();
|
||||
|
||||
Position startPosition = owner.GetPosition();
|
||||
if (!CheckBoundary(startPosition)) // fall back to creature position
|
||||
{
|
||||
startPosition = me.GetPosition();
|
||||
if (!CheckBoundary(startPosition))
|
||||
{
|
||||
startPosition = me.GetHomePosition();
|
||||
if (!CheckBoundary(startPosition)) // fall back to creature home position
|
||||
return CypherStrings.CreatureNoInteriorPointFound;
|
||||
}
|
||||
}
|
||||
float spawnZ = startPosition.GetPositionZ() + SharedConst.BoundaryVisualizeSpawnHeight;
|
||||
|
||||
bool boundsWarning = false;
|
||||
Q.Add(new KeyValuePair<int, int>(0, 0));
|
||||
while (!Q.Empty())
|
||||
{
|
||||
var front = Q.First();
|
||||
bool hasOutOfBoundsNeighbor = false;
|
||||
foreach (var off in new List<KeyValuePair<int, int>>() { new KeyValuePair<int, int>(1, 0), new KeyValuePair<int, int>(0, 1), new KeyValuePair<int, int>(-1, 0), new KeyValuePair<int, int>(0, -1) })
|
||||
{
|
||||
var next = new KeyValuePair<int, int>(front.Key + off.Key, front.Value + off.Value);
|
||||
if (next.Key > SharedConst.BoundaryVisualizeFailsafeLimit || next.Key < -SharedConst.BoundaryVisualizeFailsafeLimit || next.Value > SharedConst.BoundaryVisualizeFailsafeLimit || next.Value < -SharedConst.BoundaryVisualizeFailsafeLimit)
|
||||
{
|
||||
boundsWarning = true;
|
||||
continue;
|
||||
}
|
||||
if (!alreadyChecked.Contains(next)) // never check a coordinate twice
|
||||
{
|
||||
Position nextPos = new Position(startPosition.GetPositionX() + next.Key * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionY() + next.Value * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionZ());
|
||||
if (CheckBoundary(nextPos))
|
||||
Q.Add(next);
|
||||
else
|
||||
{
|
||||
outOfBounds.Add(next);
|
||||
hasOutOfBoundsNeighbor = true;
|
||||
}
|
||||
alreadyChecked.Add(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (outOfBounds.Contains(next))
|
||||
hasOutOfBoundsNeighbor = true;
|
||||
}
|
||||
}
|
||||
if (fill || hasOutOfBoundsNeighbor)
|
||||
{
|
||||
var pos = new Position(startPosition.GetPositionX() + front.Key * SharedConst.BoundaryVisualizeStepSize, startPosition.GetPositionY() + front.Value * SharedConst.BoundaryVisualizeStepSize, spawnZ);
|
||||
TempSummon point = owner.SummonCreature(SharedConst.BoundaryVisualizeCreature, pos, TempSummonType.TimedDespawn, (uint)(duration * Time.InMilliseconds));
|
||||
if (point)
|
||||
{
|
||||
point.SetObjectScale(SharedConst.BoundaryVisualizeCreatureScale);
|
||||
point.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToPc | UnitFlags.Stunned | UnitFlags.ImmuneToNpc);
|
||||
if (!hasOutOfBoundsNeighbor)
|
||||
point.SetFlag(UnitFields.Flags, UnitFlags.NotSelectable);
|
||||
}
|
||||
Q.Remove(front);
|
||||
}
|
||||
}
|
||||
return boundsWarning ? CypherStrings.CreatureMovementMaybeUnbounded : 0;
|
||||
}
|
||||
|
||||
public bool CheckBoundary(Position who = null)
|
||||
{
|
||||
if (who == null)
|
||||
who = me;
|
||||
|
||||
foreach (var boundary in _boundary)
|
||||
if (!boundary.IsWithinBoundary(who))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CheckInRoom()
|
||||
{
|
||||
if (CheckBoundary())
|
||||
return true;
|
||||
else
|
||||
{
|
||||
EnterEvadeMode(EvadeReason.Boundary);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Creature DoSummon(uint entry, Position pos, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn)
|
||||
{
|
||||
return me.SummonCreature(entry, pos, summonType, despawnTime);
|
||||
}
|
||||
|
||||
public Creature DoSummon(uint entry, WorldObject obj, float radius = 5.0f, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn)
|
||||
{
|
||||
Position pos = obj.GetRandomNearPosition(radius);
|
||||
return me.SummonCreature(entry, pos, summonType, despawnTime);
|
||||
}
|
||||
|
||||
public Creature DoSummonFlyer(uint entry, WorldObject obj, float flightZ, float radius = 5.0f, uint despawnTime = 30000, TempSummonType summonType = TempSummonType.CorpseTimedDespawn)
|
||||
{
|
||||
Position pos = obj.GetRandomNearPosition(radius);
|
||||
pos.posZ += flightZ;
|
||||
return me.SummonCreature(entry, pos, summonType, despawnTime);
|
||||
}
|
||||
|
||||
public void SetBoundary(List<AreaBoundary> boundary)
|
||||
{
|
||||
_boundary = boundary;
|
||||
me.DoImmediateBoundaryCheck();
|
||||
}
|
||||
|
||||
// Called for reaction at enter to combat if not in combat yet (enemy can be NULL)
|
||||
public virtual void EnterCombat(Unit victim) { }
|
||||
|
||||
// Called when the creature is killed
|
||||
public virtual void JustDied(Unit killer) { }
|
||||
|
||||
// Called when the creature kills a unit
|
||||
public virtual void KilledUnit(Unit victim) {}
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
public virtual void JustSummoned(Creature summon) { }
|
||||
public virtual void IsSummonedBy(Unit summoner) { }
|
||||
|
||||
public virtual void SummonedCreatureDespawn(Creature summon) { }
|
||||
public virtual void SummonedCreatureDies(Creature summon, Unit killer) { }
|
||||
|
||||
// Called when the creature successfully summons a gameobject
|
||||
public virtual void JustSummonedGameobject(GameObject gameobject) { }
|
||||
public virtual void SummonedGameobjectDespawn(GameObject gameobject) { }
|
||||
|
||||
// Called when the creature successfully registers a dynamicobject
|
||||
public virtual void JustRegisteredDynObject(DynamicObject dynObject) { }
|
||||
public virtual void JustUnregisteredDynObject(DynamicObject dynObject) { }
|
||||
|
||||
// Called when the creature successfully registers an areatrigger
|
||||
public virtual void JustRegisteredAreaTrigger(AreaTrigger areaTrigger) { }
|
||||
public virtual void JustUnregisteredAreaTrigger(AreaTrigger areaTrigger) { }
|
||||
|
||||
// Called when hit by a spell
|
||||
public virtual void SpellHit(Unit caster, SpellInfo spell) {}
|
||||
|
||||
// Called when spell hits a target
|
||||
public virtual void SpellHitTarget(Unit target, SpellInfo spell) {}
|
||||
|
||||
// Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc)
|
||||
public virtual void AttackedBy(Unit attacker) { }
|
||||
public virtual bool IsEscorted() { return false; }
|
||||
|
||||
// Called when creature is spawned or respawned
|
||||
public virtual void JustRespawned() { }
|
||||
|
||||
public virtual void MovementInform(MovementGeneratorType type, uint id) { }
|
||||
|
||||
// Called when a spell cast gets interrupted
|
||||
public virtual void OnSpellCastInterrupt(SpellInfo spell) { }
|
||||
|
||||
// Called at reaching home after evade
|
||||
public virtual void JustReachedHome() { }
|
||||
|
||||
// Called at text emote receive from player
|
||||
public virtual void ReceiveEmote(Player player, TextEmotes emoteId) { }
|
||||
|
||||
// Called when owner takes damage
|
||||
public virtual void OwnerAttackedBy(Unit attacker) {}
|
||||
|
||||
// Called when owner attacks something
|
||||
public virtual void OwnerAttacked(Unit target) {}
|
||||
|
||||
// called when the corpse of this creature gets removed
|
||||
public virtual void CorpseRemoved(long respawnDelay) {}
|
||||
|
||||
public virtual void PassengerBoarded(Unit passenger, sbyte seatId, bool apply) { }
|
||||
|
||||
public virtual void OnSpellClick(Unit clicker, ref bool result) { }
|
||||
|
||||
public virtual bool CanSeeAlways(WorldObject obj) { return false; }
|
||||
|
||||
// Called when a player is charmed by the creature
|
||||
// If a PlayerAI* is returned, that AI is placed on the player instead of the default charm AI
|
||||
// Object destruction is handled by Unit::RemoveCharmedBy
|
||||
public virtual PlayerAI GetAIForCharmedPlayer(Player who) { return null; }
|
||||
|
||||
List<AreaBoundary> GetBoundary() { return _boundary; }
|
||||
|
||||
bool MoveInLineOfSight_locked;
|
||||
protected new Creature me;
|
||||
List<AreaBoundary> _boundary = new List<AreaBoundary>();
|
||||
|
||||
protected EventMap _events = new EventMap();
|
||||
protected TaskScheduler _scheduler = new TaskScheduler();
|
||||
}
|
||||
|
||||
public struct AISpellInfoType
|
||||
{
|
||||
public AITarget target;
|
||||
public AICondition condition;
|
||||
public uint cooldown;
|
||||
public uint realCooldown;
|
||||
public float maxRange;
|
||||
}
|
||||
|
||||
public enum AITarget
|
||||
{
|
||||
Self,
|
||||
Victim,
|
||||
Enemy,
|
||||
Ally,
|
||||
Buff,
|
||||
Debuff
|
||||
}
|
||||
|
||||
public enum AICondition
|
||||
{
|
||||
Aggro,
|
||||
Combat,
|
||||
Die
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.Dynamic;
|
||||
using Game.Entities;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class GameObjectAI
|
||||
{
|
||||
public GameObjectAI(GameObject g)
|
||||
{
|
||||
go = g;
|
||||
_scheduler = new TaskScheduler();
|
||||
_events = new EventMap();
|
||||
}
|
||||
|
||||
public virtual void UpdateAI(uint diff) { }
|
||||
|
||||
public virtual void InitializeAI() { Reset(); }
|
||||
|
||||
public virtual void Reset() { }
|
||||
|
||||
// Pass parameters between AI
|
||||
public virtual void DoAction(int param = 0) { }
|
||||
public virtual void SetGUID(ulong guid, int id = 0) { }
|
||||
public virtual ulong GetGUID(int id = 0) { return 0; }
|
||||
|
||||
public virtual bool GossipHello(Player player, bool isUse) { return false; }
|
||||
public virtual bool GossipSelect(Player player, uint sender, uint action) { return false; }
|
||||
public virtual bool GossipSelectCode(Player player, uint sender, uint action, string code) { return false; }
|
||||
public virtual bool QuestAccept(Player player, Quest quest) { return false; }
|
||||
public virtual bool QuestReward(Player player, Quest quest, uint opt) { return false; }
|
||||
public virtual uint GetDialogStatus(Player player) { return 100; }
|
||||
public virtual void Destroyed(Player player, uint eventId) { }
|
||||
public virtual void SetData64(uint id, ulong value) { }
|
||||
public virtual ulong GetData64(uint id) { return 0; }
|
||||
public virtual uint GetData(uint id) { return 0; }
|
||||
public virtual void SetData(uint id, uint value) { }
|
||||
|
||||
public virtual void OnGameEvent(bool start, ushort eventId) { }
|
||||
public virtual void OnStateChanged(uint state, Unit unit) { }
|
||||
public virtual void EventInform(uint eventId) { }
|
||||
|
||||
protected TaskScheduler _scheduler;
|
||||
protected EventMap _events;
|
||||
|
||||
public GameObject go;
|
||||
}
|
||||
|
||||
public class NullGameObjectAI : GameObjectAI
|
||||
{
|
||||
public NullGameObjectAI(GameObject g) : base(g) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.Entities;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class GuardAI : ScriptedAI
|
||||
{
|
||||
public GuardAI(Creature creature) : base(creature) { }
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public override bool CanSeeAlways(WorldObject obj)
|
||||
{
|
||||
if (!obj.isTypeMask(TypeMask.Unit))
|
||||
return false;
|
||||
|
||||
var threatList = me.GetThreatManager().getThreatList();
|
||||
foreach (var refe in threatList)
|
||||
if (refe.getUnitGuid() == obj.GetGUID())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why)
|
||||
{
|
||||
if (!me.IsAlive())
|
||||
{
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
me.CombatStop(true);
|
||||
me.DeleteThreatList();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Unit, "Guard entry: {0} enters evade mode.", me.GetEntry());
|
||||
|
||||
me.RemoveAllAuras();
|
||||
me.DeleteThreatList();
|
||||
me.CombatStop(true);
|
||||
|
||||
// Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase)
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
Player player = killer.GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (player != null)
|
||||
me.SendZoneUnderAttackMessage(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.Entities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class PassiveAI : CreatureAI
|
||||
{
|
||||
public PassiveAI(Creature c) : base(c)
|
||||
{
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (me.IsInCombat() && me.getAttackers().Empty())
|
||||
EnterEvadeMode(EvadeReason.NoHostiles);
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit victim) { }
|
||||
|
||||
public override void MoveInLineOfSight(Unit who) { }
|
||||
}
|
||||
|
||||
public class PossessedAI : CreatureAI
|
||||
{
|
||||
public PossessedAI(Creature c) : base(c)
|
||||
{
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit target)
|
||||
{
|
||||
me.Attack(target, true);
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (me.GetVictim() != null)
|
||||
{
|
||||
if (!me.IsValidAttackTarget(me.GetVictim()))
|
||||
me.AttackStop();
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustDied(Unit unit)
|
||||
{
|
||||
// We died while possessed, disable our loot
|
||||
me.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable);
|
||||
}
|
||||
|
||||
public override void KilledUnit(Unit victim)
|
||||
{
|
||||
// We killed a creature, disable victim's loot
|
||||
if (victim.IsTypeId(TypeId.Unit))
|
||||
victim.RemoveFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable);
|
||||
}
|
||||
|
||||
public override void OnCharmed(bool apply)
|
||||
{
|
||||
me.NeedChangeAI = true;
|
||||
me.IsAIEnabled = false;
|
||||
}
|
||||
|
||||
public override void MoveInLineOfSight(Unit who) { }
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why) { }
|
||||
}
|
||||
|
||||
public class NullCreatureAI : CreatureAI
|
||||
{
|
||||
public NullCreatureAI(Creature c) : base(c)
|
||||
{
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
|
||||
public override void MoveInLineOfSight(Unit unit) { }
|
||||
public override void AttackStart(Unit unit) { }
|
||||
public override void UpdateAI(uint diff) { }
|
||||
public override void EnterEvadeMode(EvadeReason why) { }
|
||||
public override void OnCharmed(bool apply) { }
|
||||
}
|
||||
|
||||
public class CritterAI : PassiveAI
|
||||
{
|
||||
public CritterAI(Creature c) : base(c)
|
||||
{
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
|
||||
public override void DamageTaken(Unit done_by, ref uint damage)
|
||||
{
|
||||
if (!me.HasUnitState(UnitState.Fleeing))
|
||||
me.SetControlled(true, UnitState.Fleeing);
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why)
|
||||
{
|
||||
if (me.HasUnitState(UnitState.Fleeing))
|
||||
me.SetControlled(false, UnitState.Fleeing);
|
||||
base.EnterEvadeMode(why);
|
||||
}
|
||||
}
|
||||
|
||||
public class TriggerAI : NullCreatureAI
|
||||
{
|
||||
public TriggerAI(Creature c) : base(c) { }
|
||||
|
||||
public override void IsSummonedBy(Unit summoner)
|
||||
{
|
||||
if (me.m_spells[0] != 0)
|
||||
me.CastSpell(me, me.m_spells[0], false, null, null, summoner.GetGUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
* 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.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class PetAI : CreatureAI
|
||||
{
|
||||
public PetAI(Creature c) : base(c)
|
||||
{
|
||||
i_tracker = new TimeTracker(5000);
|
||||
|
||||
UpdateAllies();
|
||||
}
|
||||
|
||||
bool _needToStop()
|
||||
{
|
||||
// This is needed for charmed creatures, as once their target was reset other effects can trigger threat
|
||||
if (me.IsCharmed() && me.GetVictim() == me.GetCharmer())
|
||||
return true;
|
||||
|
||||
return !me.IsValidAttackTarget(me.GetVictim());
|
||||
}
|
||||
|
||||
void _stopAttack()
|
||||
{
|
||||
if (!me.IsAlive())
|
||||
{
|
||||
Log.outDebug(LogFilter.Server, "Creature stoped attacking cuz his dead [{0}]", me.GetGUID().ToString());
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
me.CombatStop();
|
||||
me.getHostileRefManager().deleteReferences();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
me.AttackStop();
|
||||
me.InterruptNonMeleeSpells(false);
|
||||
me.SendMeleeAttackStop(); // Should stop pet's attack button from flashing
|
||||
me.GetCharmInfo().SetIsCommandAttack(false);
|
||||
ClearCharmInfoFlags();
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!me.IsAlive() || me.GetCharmInfo() == null)
|
||||
return;
|
||||
|
||||
Unit owner = me.GetCharmerOrOwner();
|
||||
|
||||
if (m_updateAlliesTimer <= diff)
|
||||
// UpdateAllies self set update timer
|
||||
UpdateAllies();
|
||||
else
|
||||
m_updateAlliesTimer -= diff;
|
||||
|
||||
if (me.GetVictim() && me.GetVictim().IsAlive())
|
||||
{
|
||||
// is only necessary to stop casting, the pet must not exit combat
|
||||
if (!me.GetCurrentSpell(CurrentSpellTypes.Channeled) && // ignore channeled spells (Pin, Seduction)
|
||||
(me.GetVictim() && me.GetVictim().HasBreakableByDamageCrowdControlAura(me)))
|
||||
{
|
||||
me.InterruptNonMeleeSpells(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_needToStop())
|
||||
{
|
||||
Log.outDebug(LogFilter.Server, "Pet AI stopped attacking [{0}]", me.GetGUID().ToString());
|
||||
_stopAttack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check before attacking to prevent pets from leaving stay position
|
||||
if (me.GetCharmInfo().HasCommandState(CommandStates.Stay))
|
||||
{
|
||||
if (me.GetCharmInfo().IsCommandAttack() || (me.GetCharmInfo().IsAtStay() && me.IsWithinMeleeRange(me.GetVictim())))
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me.HasReactState(ReactStates.Aggressive) || me.GetCharmInfo().IsAtStay())
|
||||
{
|
||||
// Every update we need to check targets only in certain cases
|
||||
// Aggressive - Allow auto select if owner or pet don't have a target
|
||||
// Stay - Only pick from pet or owner targets / attackers so targets won't run by
|
||||
// while chasing our owner. Don't do auto select.
|
||||
// All other cases (ie: defensive) - Targets are assigned by AttackedBy(), OwnerAttackedBy(), OwnerAttacked(), etc.
|
||||
Unit nextTarget = SelectNextTarget(me.HasReactState(ReactStates.Aggressive));
|
||||
|
||||
if (nextTarget)
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
// Autocast (casted only in combat or persistent spells in any state)
|
||||
if (!me.HasUnitState(UnitState.Casting))
|
||||
{
|
||||
List<Tuple<Unit, Spell>> targetSpellStore = new List<Tuple<Unit, Spell>>();
|
||||
|
||||
for (byte i = 0; i < me.GetPetAutoSpellSize(); ++i)
|
||||
{
|
||||
uint spellID = me.GetPetAutoSpellOnPos(i);
|
||||
if (spellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellID);
|
||||
if (spellInfo == null)
|
||||
continue;
|
||||
|
||||
if (me.GetCharmInfo() != null && me.GetSpellHistory().HasGlobalCooldown(spellInfo))
|
||||
continue;
|
||||
|
||||
// check spell cooldown
|
||||
if (!me.GetSpellHistory().IsReady(spellInfo))
|
||||
continue;
|
||||
|
||||
if (spellInfo.IsPositive())
|
||||
{
|
||||
if (spellInfo.CanBeUsedInCombat())
|
||||
{
|
||||
// Check if we're in combat or commanded to attack
|
||||
if (!me.IsInCombat() && !me.GetCharmInfo().IsCommandAttack())
|
||||
continue;
|
||||
}
|
||||
|
||||
Spell spell = new Spell(me, spellInfo, TriggerCastFlags.None);
|
||||
bool spellUsed = false;
|
||||
|
||||
// Some spells can target enemy or friendly (DK Ghoul's Leap)
|
||||
// Check for enemy first (pet then owner)
|
||||
Unit target = me.getAttackerForHelper();
|
||||
if (!target && owner)
|
||||
target = owner.getAttackerForHelper();
|
||||
|
||||
if (target)
|
||||
{
|
||||
if (CanAIAttack(target) && spell.CanAutoCast(target))
|
||||
{
|
||||
targetSpellStore.Add(Tuple.Create(target, spell));
|
||||
spellUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (spellInfo.HasEffect(SpellEffectName.JumpDest))
|
||||
{
|
||||
if (!spellUsed)
|
||||
spell.Dispose();
|
||||
continue; // Pets must only jump to target
|
||||
}
|
||||
|
||||
// No enemy, check friendly
|
||||
if (!spellUsed)
|
||||
{
|
||||
foreach (var tar in m_AllySet)
|
||||
{
|
||||
Unit ally = Global.ObjAccessor.GetUnit(me, tar);
|
||||
|
||||
//only buff targets that are in combat, unless the spell can only be cast while out of combat
|
||||
if (!ally)
|
||||
continue;
|
||||
|
||||
if (spell.CanAutoCast(ally))
|
||||
{
|
||||
targetSpellStore.Add(Tuple.Create(ally, spell));
|
||||
spellUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No valid targets at all
|
||||
if (!spellUsed)
|
||||
spell.Dispose();
|
||||
}
|
||||
else if (me.GetVictim() && CanAIAttack(me.GetVictim()) && spellInfo.CanBeUsedInCombat())
|
||||
{
|
||||
Spell spell = new Spell(me, spellInfo, TriggerCastFlags.None);
|
||||
if (spell.CanAutoCast(me.GetVictim()))
|
||||
targetSpellStore.Add(Tuple.Create(me.GetVictim(), spell));
|
||||
else
|
||||
spell.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
//found units to cast on to
|
||||
if (!targetSpellStore.Empty())
|
||||
{
|
||||
int index = RandomHelper.IRand(0, targetSpellStore.Count - 1);
|
||||
var tss = targetSpellStore[index];
|
||||
|
||||
(Unit target, Spell spell) = tss;
|
||||
|
||||
targetSpellStore.RemoveAt(index);
|
||||
|
||||
SpellCastTargets targets = new SpellCastTargets();
|
||||
targets.SetUnitTarget(target);
|
||||
|
||||
spell.prepare(targets);
|
||||
}
|
||||
|
||||
// deleted cached Spell objects
|
||||
foreach (var pair in targetSpellStore)
|
||||
pair.Item2.Dispose();
|
||||
}
|
||||
|
||||
// Update speed as needed to prevent dropping too far behind and despawning
|
||||
me.UpdateSpeed(UnitMoveType.Run);
|
||||
me.UpdateSpeed(UnitMoveType.Walk);
|
||||
me.UpdateSpeed(UnitMoveType.Flight);
|
||||
}
|
||||
|
||||
void UpdateAllies()
|
||||
{
|
||||
m_updateAlliesTimer = 10 * Time.InMilliseconds; // update friendly targets every 10 seconds, lesser checks increase performance
|
||||
|
||||
Unit owner = me.GetCharmerOrOwner();
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Group group = null;
|
||||
Player player = owner.ToPlayer();
|
||||
if (player)
|
||||
group = player.GetGroup();
|
||||
|
||||
//only pet and owner/not in group.ok
|
||||
if (m_AllySet.Count == 2 && !group)
|
||||
return;
|
||||
|
||||
//owner is in group; group members filled in already (no raid . subgroupcount = whole count)
|
||||
if (group && !group.isRaidGroup() && m_AllySet.Count == (group.GetMembersCount() + 2))
|
||||
return;
|
||||
|
||||
m_AllySet.Clear();
|
||||
m_AllySet.Add(me.GetGUID());
|
||||
if (group) //add group
|
||||
{
|
||||
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
||||
{
|
||||
Player Target = refe.GetSource();
|
||||
if (!Target || !group.SameSubGroup(owner.ToPlayer(), Target))
|
||||
continue;
|
||||
|
||||
if (Target.GetGUID() == owner.GetGUID())
|
||||
continue;
|
||||
|
||||
m_AllySet.Add(Target.GetGUID());
|
||||
}
|
||||
}
|
||||
else //remove group
|
||||
m_AllySet.Add(owner.GetGUID());
|
||||
}
|
||||
|
||||
public override void KilledUnit(Unit victim)
|
||||
{
|
||||
// Called from Unit.Kill() in case where pet or owner kills something
|
||||
// if owner killed this victim, pet may still be attacking something else
|
||||
if (me.GetVictim() && me.GetVictim() != victim)
|
||||
return;
|
||||
|
||||
// Clear target just in case. May help problem where health / focus / mana
|
||||
// regen gets stuck. Also resets attack command.
|
||||
// Can't use _stopAttack() because that activates movement handlers and ignores
|
||||
// next target selection
|
||||
me.AttackStop();
|
||||
me.InterruptNonMeleeSpells(false);
|
||||
me.SendMeleeAttackStop(); // Stops the pet's 'Attack' button from flashing
|
||||
|
||||
// Before returning to owner, see if there are more things to attack
|
||||
Unit nextTarget = SelectNextTarget(false);
|
||||
if (nextTarget)
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement(); // Return
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit victim)
|
||||
{
|
||||
// Overrides Unit.AttackStart to correctly evaluate Pet states
|
||||
|
||||
// Check all pet states to decide if we can attack this target
|
||||
if (!CanAIAttack(victim))
|
||||
return;
|
||||
|
||||
// Only chase if not commanded to stay or if stay but commanded to attack
|
||||
DoAttack(victim, (!me.GetCharmInfo().HasCommandState(CommandStates.Stay) || me.GetCharmInfo().IsCommandAttack()));
|
||||
}
|
||||
|
||||
public override void OwnerAttackedBy(Unit attacker)
|
||||
{
|
||||
// Called when owner takes damage. This function helps keep pets from running off
|
||||
// simply due to owner gaining aggro.
|
||||
|
||||
if (!attacker)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me.GetVictim() && me.GetVictim().IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(attacker);
|
||||
}
|
||||
|
||||
public override void OwnerAttacked(Unit target)
|
||||
{
|
||||
// Called when owner attacks something. Allows defensive pets to know
|
||||
// that they need to assist
|
||||
|
||||
// Target might be null if called from spell with invalid cast targets
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me.GetVictim() && me.GetVictim().IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(target);
|
||||
}
|
||||
|
||||
Unit SelectNextTarget(bool allowAutoSelect)
|
||||
{
|
||||
// Provides next target selection after current target death.
|
||||
// This function should only be called internally by the AI
|
||||
// Targets are not evaluated here for being valid targets, that is done in _CanAttack()
|
||||
// The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations
|
||||
|
||||
// Passive pets don't do next target selection
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
return null;
|
||||
|
||||
// Check pet attackers first so we don't drag a bunch of targets to the owner
|
||||
Unit myAttacker = me.getAttackerForHelper();
|
||||
if (myAttacker)
|
||||
if (!myAttacker.HasBreakableByDamageCrowdControlAura())
|
||||
return myAttacker;
|
||||
|
||||
// Not sure why we wouldn't have an owner but just in case...
|
||||
if (!me.GetCharmerOrOwner())
|
||||
return null;
|
||||
|
||||
// Check owner attackers
|
||||
Unit ownerAttacker = me.GetCharmerOrOwner().getAttackerForHelper();
|
||||
if (ownerAttacker)
|
||||
if (!ownerAttacker.HasBreakableByDamageCrowdControlAura())
|
||||
return ownerAttacker;
|
||||
|
||||
// Check owner victim
|
||||
// 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does
|
||||
Unit ownerVictim = me.GetCharmerOrOwner().GetVictim();
|
||||
if (ownerVictim)
|
||||
return ownerVictim;
|
||||
|
||||
// Neither pet or owner had a target and aggressive pets can pick any target
|
||||
// To prevent aggressive pets from chain selecting targets and running off, we
|
||||
// only select a random target if certain conditions are met.
|
||||
if (me.HasReactState(ReactStates.Aggressive) && allowAutoSelect)
|
||||
{
|
||||
if (!me.GetCharmInfo().IsReturning() || me.GetCharmInfo().IsFollowing() || me.GetCharmInfo().IsAtStay())
|
||||
{
|
||||
Unit nearTarget = me.SelectNearestHostileUnitInAggroRange(true);
|
||||
if (nearTarget)
|
||||
return nearTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// Default - no valid targets
|
||||
return null;
|
||||
}
|
||||
|
||||
void HandleReturnMovement()
|
||||
{
|
||||
// Handles moving the pet back to stay or owner
|
||||
|
||||
// Prevent activating movement when under control of spells
|
||||
// such as "Eyes of the Beast"
|
||||
if (me.IsCharmed())
|
||||
return;
|
||||
|
||||
if (me.GetCharmInfo().HasCommandState(CommandStates.Stay))
|
||||
{
|
||||
if (!me.GetCharmInfo().IsAtStay() && !me.GetCharmInfo().IsReturning())
|
||||
{
|
||||
// Return to previous position where stay was clicked
|
||||
float x, y, z;
|
||||
|
||||
me.GetCharmInfo().GetStayPosition(out x, out y, out z);
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsReturning(true);
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MovePoint(me.GetGUID().GetCounter(), x, y, z);
|
||||
}
|
||||
}
|
||||
else // COMMAND_FOLLOW
|
||||
{
|
||||
if (!me.GetCharmInfo().IsFollowing() && !me.GetCharmInfo().IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsReturning(true);
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveFollow(me.GetCharmerOrOwner(), SharedConst.PetFollowDist, me.GetFollowAngle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoAttack(Unit target, bool chase)
|
||||
{
|
||||
// Handles attack with or without chase and also resets flags
|
||||
// for next update / creature kill
|
||||
|
||||
if (me.Attack(target, true))
|
||||
{
|
||||
Unit owner = me.GetOwner();
|
||||
if (owner)
|
||||
owner.SetInCombatWith(target);
|
||||
|
||||
// Play sound to let the player know the pet is attacking something it picked on its own
|
||||
if (me.HasReactState(ReactStates.Aggressive) && !me.GetCharmInfo().IsCommandAttack())
|
||||
me.SendPetAIReaction(me.GetGUID());
|
||||
|
||||
if (chase)
|
||||
{
|
||||
bool oldCmdAttack = me.GetCharmInfo().IsCommandAttack(); // This needs to be reset after other flags are cleared
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveChase(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsAtStay(true);
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType type, uint id)
|
||||
{
|
||||
// Receives notification when pet reaches stay or follow owner
|
||||
switch (type)
|
||||
{
|
||||
case MovementGeneratorType.Point:
|
||||
{
|
||||
// Pet is returning to where stay was clicked. data should be
|
||||
// pet's GUIDLow since we set that as the waypoint ID
|
||||
if (id == me.GetGUID().GetCounter() && me.GetCharmInfo().IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsAtStay(true);
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MovementGeneratorType.Follow:
|
||||
{
|
||||
// If data is owner's GUIDLow then we've reached follow point,
|
||||
// otherwise we're probably chasing a creature
|
||||
if (me.GetCharmerOrOwner() && me.GetCharmInfo() != null && id == me.GetCharmerOrOwner().GetGUID().GetCounter() && me.GetCharmInfo().IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me.GetCharmInfo().SetIsFollowing(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanAIAttack(Unit victim)
|
||||
{
|
||||
// Evaluates wether a pet can attack a specific target based on CommandState, ReactState and other flags
|
||||
// IMPORTANT: The order in which things are checked is important, be careful if you add or remove checks
|
||||
|
||||
// Hmmm...
|
||||
if (!victim)
|
||||
return false;
|
||||
|
||||
if (!victim.IsAlive())
|
||||
{
|
||||
// Clear target to prevent getting stuck on dead targets
|
||||
me.AttackStop();
|
||||
me.InterruptNonMeleeSpells(false);
|
||||
me.SendMeleeAttackStop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Passive - passive pets can attack if told to
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
return me.GetCharmInfo().IsCommandAttack();
|
||||
|
||||
// CC - mobs under crowd control can be attacked if owner commanded
|
||||
if (victim.HasBreakableByDamageCrowdControlAura())
|
||||
return me.GetCharmInfo().IsCommandAttack();
|
||||
|
||||
// Returning - pets ignore attacks only if owner clicked follow
|
||||
if (me.GetCharmInfo().IsReturning())
|
||||
return !me.GetCharmInfo().IsCommandFollow();
|
||||
|
||||
// Stay - can attack if target is within range or commanded to
|
||||
if (me.GetCharmInfo().HasCommandState(CommandStates.Stay))
|
||||
return (me.IsWithinMeleeRange(victim) || me.GetCharmInfo().IsCommandAttack());
|
||||
|
||||
// Pets attacking something (or chasing) should only switch targets if owner tells them to
|
||||
if (me.GetVictim() && me.GetVictim() != victim)
|
||||
{
|
||||
// Check if our owner selected this target and clicked "attack"
|
||||
Unit ownerTarget = null;
|
||||
Player owner = me.GetCharmerOrOwner().ToPlayer();
|
||||
if (owner)
|
||||
ownerTarget = owner.GetSelectedUnit();
|
||||
else
|
||||
ownerTarget = me.GetCharmerOrOwner().GetVictim();
|
||||
|
||||
if (ownerTarget && me.GetCharmInfo().IsCommandAttack())
|
||||
return (victim.GetGUID() == ownerTarget.GetGUID());
|
||||
}
|
||||
|
||||
// Follow
|
||||
if (me.GetCharmInfo().HasCommandState(CommandStates.Follow))
|
||||
return !me.GetCharmInfo().IsReturning();
|
||||
|
||||
// default, though we shouldn't ever get here
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ReceiveEmote(Player player, TextEmotes emoteId)
|
||||
{
|
||||
if (!me.GetOwnerGUID().IsEmpty() && me.GetOwnerGUID() == player.GetGUID())
|
||||
switch (emoteId)
|
||||
{
|
||||
case TextEmotes.Cower:
|
||||
if (me.IsPet() && me.ToPet().IsPetGhoul())
|
||||
me.HandleEmoteCommand(Emote.OneshotOmnicastGhoul);
|
||||
break;
|
||||
case TextEmotes.Angry:
|
||||
if (me.IsPet() && me.ToPet().IsPetGhoul())
|
||||
me.HandleEmoteCommand(Emote.StateStun);
|
||||
break;
|
||||
case TextEmotes.Glare:
|
||||
if (me.IsPet() && me.ToPet().IsPetGhoul())
|
||||
me.HandleEmoteCommand(Emote.StateStun);
|
||||
break;
|
||||
case TextEmotes.Soothe:
|
||||
if (me.IsPet() && me.ToPet().IsPetGhoul())
|
||||
me.HandleEmoteCommand( Emote.OneshotOmnicastGhoul);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCharmed(bool apply)
|
||||
{
|
||||
me.NeedChangeAI = true;
|
||||
me.IsAIEnabled = false;
|
||||
}
|
||||
|
||||
void ClearCharmInfoFlags()
|
||||
{
|
||||
// Quick access to set all flags to FALSE
|
||||
|
||||
CharmInfo ci = me.GetCharmInfo();
|
||||
|
||||
if (ci != null)
|
||||
{
|
||||
ci.SetIsAtStay(false);
|
||||
ci.SetIsCommandAttack(false);
|
||||
ci.SetIsCommandFollow(false);
|
||||
ci.SetIsFollowing(false);
|
||||
ci.SetIsReturning(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AttackedBy(Unit attacker)
|
||||
{
|
||||
// Called when pet takes damage. This function helps keep pets from running off
|
||||
// simply due to gaining aggro.
|
||||
|
||||
if (!attacker)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me.HasReactState( ReactStates.Passive))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me.GetVictim() && me.GetVictim().IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(attacker);
|
||||
}
|
||||
|
||||
// The following aren't used by the PetAI but need to be defined to override
|
||||
// default CreatureAI functions which interfere with the PetAI
|
||||
public override void MoveInLineOfSight(Unit who) { }
|
||||
public override void MoveInLineOfSight_Safe(Unit who) { }
|
||||
public override void EnterEvadeMode(EvadeReason why) { }
|
||||
|
||||
TimeTracker i_tracker;
|
||||
List<ObjectGuid> m_AllySet = new List<ObjectGuid>();
|
||||
uint m_updateAlliesTimer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.Entities;
|
||||
using Game.Maps;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class TotemAI : CreatureAI
|
||||
{
|
||||
public TotemAI(Creature c) : base(c)
|
||||
{
|
||||
i_victimGuid = ObjectGuid.Empty;
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why)
|
||||
{
|
||||
me.CombatStop(true);
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (me.ToTotem().GetTotemType() != TotemType.Active)
|
||||
return;
|
||||
|
||||
if (!me.IsAlive() || me.IsNonMeleeSpellCast(false))
|
||||
return;
|
||||
|
||||
// Search spell
|
||||
var spellInfo = Global.SpellMgr.GetSpellInfo(me.ToTotem().GetSpell());
|
||||
if (spellInfo == null)
|
||||
return;
|
||||
|
||||
// Get spell range
|
||||
float max_range = spellInfo.GetMaxRange(false);
|
||||
|
||||
// SPELLMOD_RANGE not applied in this place just because not existence range mods for attacking totems
|
||||
|
||||
Unit victim = !i_victimGuid.IsEmpty() ? Global.ObjAccessor.GetUnit(me, i_victimGuid) : null;
|
||||
|
||||
// Search victim if no, not attackable, or out of range, or friendly (possible in case duel end)
|
||||
if (victim == null || !victim.isTargetableForAttack() || !me.IsWithinDistInMap(victim, max_range) ||
|
||||
me.IsFriendlyTo(victim) || !me.CanSeeOrDetect(victim))
|
||||
{
|
||||
victim = null;
|
||||
var u_check = new NearestAttackableUnitInObjectRangeCheck(me, me.GetCharmerOrOwnerOrSelf(), max_range);
|
||||
var checker = new UnitLastSearcher(me, u_check);
|
||||
Cell.VisitAllObjects(me, checker, max_range);
|
||||
victim = checker.GetTarget();
|
||||
}
|
||||
|
||||
// If have target
|
||||
if (victim != null)
|
||||
{
|
||||
// remember
|
||||
i_victimGuid = victim.GetGUID();
|
||||
|
||||
// attack
|
||||
me.SetInFront(victim); // client change orientation by self
|
||||
me.CastSpell(victim, me.ToTotem().GetSpell(), false);
|
||||
}
|
||||
else
|
||||
i_victimGuid.Clear();
|
||||
}
|
||||
|
||||
ObjectGuid i_victimGuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user