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,129 @@
|
||||
/*
|
||||
* 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.Movement;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class AISelector
|
||||
{
|
||||
public static CreatureAI SelectAI(Creature creature)
|
||||
{
|
||||
if (creature.IsPet())
|
||||
return new PetAI(creature);
|
||||
|
||||
//scriptname in db
|
||||
CreatureAI scriptedAI = Global.ScriptMgr.GetCreatureAI(creature);
|
||||
if (scriptedAI != null)
|
||||
return scriptedAI;
|
||||
|
||||
switch (creature.GetCreatureTemplate().AIName)
|
||||
{
|
||||
case "AggressorAI":
|
||||
return new AggressorAI(creature);
|
||||
case "ArcherAI":
|
||||
return new ArcherAI(creature);
|
||||
case "CombatAI":
|
||||
return new CombatAI(creature);
|
||||
case "CritterAI":
|
||||
return new CritterAI(creature);
|
||||
case "GuardAI":
|
||||
return new GuardAI(creature);
|
||||
case "NullCreatureAI":
|
||||
return new NullCreatureAI(creature);
|
||||
case "PassiveAI":
|
||||
return new PassiveAI(creature);
|
||||
case "PetAI":
|
||||
return new PetAI(creature);
|
||||
case "ReactorAI":
|
||||
return new ReactorAI(creature);
|
||||
case "SmartAI":
|
||||
return new SmartAI(creature);
|
||||
case "TotemAI":
|
||||
return new TotemAI(creature);
|
||||
case "TriggerAI":
|
||||
return new TriggerAI(creature);
|
||||
case "TurretAI":
|
||||
return new TurretAI(creature);
|
||||
case "VehicleAI":
|
||||
return new VehicleAI(creature);
|
||||
}
|
||||
|
||||
// select by NPC flags
|
||||
if (creature.IsVehicle())
|
||||
return new VehicleAI(creature);
|
||||
else if (creature.HasUnitTypeMask(UnitTypeMask.ControlableGuardian) && ((Guardian)creature).GetOwner().IsTypeId(TypeId.Player))
|
||||
return new PetAI(creature);
|
||||
else if (creature.HasFlag64(UnitFields.NpcFlags, NPCFlags.SpellClick))
|
||||
return new NullCreatureAI(creature);
|
||||
else if (creature.IsGuard())
|
||||
return new GuardAI(creature);
|
||||
else if (creature.HasUnitTypeMask(UnitTypeMask.ControlableGuardian))
|
||||
return new PetAI(creature);
|
||||
else if (creature.IsTotem())
|
||||
return new TotemAI(creature);
|
||||
else if (creature.IsTrigger())
|
||||
{
|
||||
if (creature.m_spells[0] != 0)
|
||||
return new TriggerAI(creature);
|
||||
else
|
||||
return new NullCreatureAI(creature);
|
||||
}
|
||||
else if (creature.IsCritter() && !creature.HasUnitTypeMask(UnitTypeMask.Guardian))
|
||||
return new CritterAI(creature);
|
||||
|
||||
if (!creature.IsCivilian() && !creature.IsNeutralToAll())
|
||||
return new AggressorAI(creature);
|
||||
|
||||
if (creature.IsCivilian() || creature.IsNeutralToAll())
|
||||
return new ReactorAI(creature);
|
||||
|
||||
return new NullCreatureAI(creature);
|
||||
}
|
||||
|
||||
public static IMovementGenerator SelectMovementAI(Creature creature)
|
||||
{
|
||||
switch (creature.m_defaultMovementType)
|
||||
{
|
||||
case MovementGeneratorType.Random:
|
||||
return new RandomMovementGenerator<Creature>();
|
||||
case MovementGeneratorType.Waypoint:
|
||||
return new WaypointMovementGenerator<Creature>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GameObjectAI SelectGameObjectAI(GameObject go)
|
||||
{
|
||||
// scriptname in db
|
||||
GameObjectAI scriptedAI = Global.ScriptMgr.GetGameObjectAI(go);
|
||||
if (scriptedAI != null)
|
||||
return scriptedAI;
|
||||
|
||||
switch (go.GetAIName())
|
||||
{
|
||||
case "GameObjectAI":
|
||||
return new GameObjectAI(go);
|
||||
case "SmartGameObjectAI":
|
||||
return new SmartGameObjectAI(go);
|
||||
}
|
||||
return new NullGameObjectAI(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,803 @@
|
||||
/*
|
||||
* 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.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Maps;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class ScriptedAI : CreatureAI
|
||||
{
|
||||
public ScriptedAI(Creature creature) : base(creature)
|
||||
{
|
||||
_isCombatMovementAllowed = true;
|
||||
_isHeroic = me.GetMap().IsHeroic();
|
||||
_difficulty = me.GetMap().GetSpawnMode();
|
||||
}
|
||||
|
||||
void AttackStartNoMove(Unit target)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (me.Attack(target, true))
|
||||
DoStartNoMovement(target);
|
||||
}
|
||||
|
||||
// Called before EnterCombat even before the creature is in combat.
|
||||
public override void AttackStart(Unit target)
|
||||
{
|
||||
if (IsCombatMovementAllowed())
|
||||
base.AttackStart(target);
|
||||
else
|
||||
AttackStartNoMove(target);
|
||||
}
|
||||
|
||||
//Called at World update tick
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
//Check if we have a current target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
//Start movement toward victim
|
||||
public void DoStartMovement(Unit target, float distance = 0.0f, float angle = 0.0f)
|
||||
{
|
||||
if (target != null)
|
||||
me.GetMotionMaster().MoveChase(target, distance, angle);
|
||||
}
|
||||
|
||||
//Start no movement on victim
|
||||
public void DoStartNoMovement(Unit target)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
|
||||
//Stop attack of current victim
|
||||
public void DoStopAttack()
|
||||
{
|
||||
if (me.GetVictim() != null)
|
||||
me.AttackStop();
|
||||
}
|
||||
|
||||
//Cast spell by spell info
|
||||
void DoCastSpell(Unit target, SpellInfo spellInfo, bool triggered = false)
|
||||
{
|
||||
if (target == null || me.IsNonMeleeSpellCast(false))
|
||||
return;
|
||||
|
||||
me.StopMoving();
|
||||
me.CastSpell(target, spellInfo, triggered ? TriggerCastFlags.FullMask : TriggerCastFlags.None);
|
||||
}
|
||||
|
||||
//Plays a sound to all nearby players
|
||||
public void DoPlaySoundToSet(WorldObject source, uint soundId)
|
||||
{
|
||||
if (source == null)
|
||||
return;
|
||||
|
||||
if (!CliDB.SoundKitStorage.ContainsKey(soundId))
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "Invalid soundId {0} used in DoPlaySoundToSet (Source: TypeId {1}, GUID {2})", soundId, source.GetTypeId(), source.GetGUID().ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
source.PlayDirectSound(soundId);
|
||||
}
|
||||
|
||||
//Spawns a creature relative to me
|
||||
public Creature DoSpawnCreature(uint entry, float offsetX, float offsetY, float offsetZ, float angle, TempSummonType type, uint despawntime)
|
||||
{
|
||||
return me.SummonCreature(entry, me.GetPositionX() + offsetX, me.GetPositionY() + offsetY, me.GetPositionZ() + offsetZ, angle, type, despawntime);
|
||||
}
|
||||
|
||||
//Returns spells that meet the specified criteria from the creatures spell list
|
||||
public SpellInfo SelectSpell(Unit target, SpellSchoolMask school, Mechanics mechanic, SelectTargetType targets, float rangeMin, float rangeMax, SelectEffect effect)
|
||||
{
|
||||
//No target so we can't cast
|
||||
if (target == null)
|
||||
return null;
|
||||
|
||||
//Silenced so we can't cast
|
||||
if (me.HasFlag(UnitFields.Flags, UnitFlags.Silenced))
|
||||
return null;
|
||||
|
||||
//Using the extended script system we first create a list of viable spells
|
||||
SpellInfo[] apSpell = new SpellInfo[SharedConst.MaxCreatureSpells];
|
||||
|
||||
uint spellCount = 0;
|
||||
|
||||
SpellInfo tempSpell = null;
|
||||
|
||||
//Check if each spell is viable(set it to null if not)
|
||||
for (uint i = 0; i < SharedConst.MaxCreatureSpells; i++)
|
||||
{
|
||||
tempSpell = Global.SpellMgr.GetSpellInfo(me.m_spells[i]);
|
||||
|
||||
//This spell doesn't exist
|
||||
if (tempSpell == null)
|
||||
continue;
|
||||
|
||||
// Targets and Effects checked first as most used restrictions
|
||||
//Check the spell targets if specified
|
||||
if (targets != 0 && !Convert.ToBoolean(Global.ScriptMgr.spellSummaryStorage[me.m_spells[i]].Targets & (1 << ((int)targets - 1))))
|
||||
continue;
|
||||
|
||||
//Check the type of spell if we are looking for a specific spell type
|
||||
if (effect != 0 && !Convert.ToBoolean(Global.ScriptMgr.spellSummaryStorage[me.m_spells[i]].Effects & (1 << ((int)effect - 1))))
|
||||
continue;
|
||||
|
||||
//Check for school if specified
|
||||
if (school != 0 && (tempSpell.SchoolMask & school) == 0)
|
||||
continue;
|
||||
|
||||
//Check for spell mechanic if specified
|
||||
if (mechanic != 0 && tempSpell.Mechanic != mechanic)
|
||||
continue;
|
||||
|
||||
//Check if the spell meets our range requirements
|
||||
if (rangeMin != 0 && me.GetSpellMinRangeForTarget(target, tempSpell) < rangeMin)
|
||||
continue;
|
||||
if (rangeMax != 0 && me.GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax)
|
||||
continue;
|
||||
|
||||
//Check if our target is in range
|
||||
if (me.IsWithinDistInMap(target, me.GetSpellMinRangeForTarget(target, tempSpell)) || !me.IsWithinDistInMap(target, me.GetSpellMaxRangeForTarget(target, tempSpell)))
|
||||
continue;
|
||||
|
||||
//All good so lets add it to the spell list
|
||||
apSpell[spellCount] = tempSpell;
|
||||
++spellCount;
|
||||
}
|
||||
|
||||
//We got our usable spells so now lets randomly pick one
|
||||
if (spellCount == 0)
|
||||
return null;
|
||||
|
||||
return apSpell[RandomHelper.IRand(0, (int)(spellCount - 1))];
|
||||
}
|
||||
|
||||
//Drops all threat to 0%. Does not remove players from the threat list
|
||||
public void DoResetThreat()
|
||||
{
|
||||
if (!me.CanHaveThreatList() || me.GetThreatManager().isThreatListEmpty())
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = {0})", me.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
var threatlist = me.GetThreatManager().getThreatList();
|
||||
|
||||
foreach (var refe in threatlist)
|
||||
{
|
||||
Unit unit = Global.ObjAccessor.GetUnit(me, refe.getUnitGuid());
|
||||
if (unit != null && DoGetThreat(unit) != 0)
|
||||
DoModifyThreatPercent(unit, -100);
|
||||
}
|
||||
}
|
||||
|
||||
public float DoGetThreat(Unit unit)
|
||||
{
|
||||
if (unit == null)
|
||||
return 0.0f;
|
||||
return me.GetThreatManager().getThreat(unit);
|
||||
}
|
||||
|
||||
public void DoModifyThreatPercent(Unit unit, int pct)
|
||||
{
|
||||
if (unit == null)
|
||||
return;
|
||||
me.GetThreatManager().modifyThreatPercent(unit, pct);
|
||||
}
|
||||
|
||||
void DoTeleportTo(float x, float y, float z, uint time = 0)
|
||||
{
|
||||
me.Relocate(x, y, z);
|
||||
float speed = me.GetDistance(x, y, z) / (time * 0.001f);
|
||||
me.MonsterMoveWithSpeed(x, y, z, speed);
|
||||
}
|
||||
|
||||
void DoTeleportTo(float[] position)
|
||||
{
|
||||
me.NearTeleportTo(position[0], position[1], position[2], position[3]);
|
||||
}
|
||||
|
||||
//Teleports a player without dropping threat (only teleports to same map)
|
||||
void DoTeleportPlayer(Unit unit, float x, float y, float z, float o)
|
||||
{
|
||||
if (unit == null)
|
||||
return;
|
||||
Player player = unit.ToPlayer();
|
||||
if (player != null)
|
||||
player.TeleportTo(unit.GetMapId(), x, y, z, o, TeleportToOptions.NotLeaveCombat);
|
||||
else
|
||||
Log.outError(LogFilter.Scripts, "Creature {0} (Entry: {1}) Tried to teleport non-player unit (Type: {2} GUID: {3}) to X: {4} Y: {5} Z: {6} O: {7}. Aborted.",
|
||||
me.GetGUID(), me.GetEntry(), unit.GetTypeId(), unit.GetGUID(), x, y, z, o);
|
||||
}
|
||||
|
||||
void DoTeleportAll(float x, float y, float z, float o)
|
||||
{
|
||||
Map map = me.GetMap();
|
||||
if (!map.IsDungeon())
|
||||
return;
|
||||
|
||||
var PlayerList = map.GetPlayers();
|
||||
foreach (var player in PlayerList)
|
||||
if (player.IsAlive())
|
||||
player.TeleportTo(me.GetMapId(), x, y, z, o, TeleportToOptions.NotLeaveCombat);
|
||||
|
||||
}
|
||||
|
||||
//Returns friendly unit with the most amount of hp missing from max hp
|
||||
public Unit DoSelectLowestHpFriendly(float range, uint minHPDiff = 1)
|
||||
{
|
||||
var u_check = new MostHPMissingInRange<Unit>(me, range, minHPDiff);
|
||||
var searcher = new UnitLastSearcher(me, u_check);
|
||||
Cell.VisitAllObjects(me, searcher, range);
|
||||
|
||||
return searcher.GetTarget();
|
||||
}
|
||||
|
||||
//Returns a list of friendly CC'd units within range
|
||||
List<Creature> DoFindFriendlyCC(float range)
|
||||
{
|
||||
List<Creature> list = new List<Creature>();
|
||||
var u_check = new FriendlyCCedInRange(me, range);
|
||||
var searcher = new CreatureListSearcher(me, list, u_check);
|
||||
Cell.VisitAllObjects(me, searcher, range);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
//Returns a list of all friendly units missing a specific buff within range
|
||||
public List<Creature> DoFindFriendlyMissingBuff(float range, uint spellId)
|
||||
{
|
||||
List<Creature> list = new List<Creature>();
|
||||
var u_check = new FriendlyMissingBuffInRange(me, range, spellId);
|
||||
var searcher = new CreatureListSearcher(me, list, u_check);
|
||||
Cell.VisitAllObjects(me, searcher, range);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
//Return a player with at least minimumRange from me
|
||||
Player GetPlayerAtMinimumRange(float minimumRange)
|
||||
{
|
||||
var check = new PlayerAtMinimumRangeAway(me, minimumRange);
|
||||
var searcher = new PlayerSearcher(me, check);
|
||||
Cell.VisitWorldObjects(me, searcher, minimumRange);
|
||||
|
||||
return searcher.GetTarget();
|
||||
}
|
||||
|
||||
public void SetEquipmentSlots(bool loadDefault, int mainHand = -1, int offHand = -1, int ranged = -1)
|
||||
{
|
||||
if (loadDefault)
|
||||
{
|
||||
me.LoadEquipment(me.GetOriginalEquipmentId(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainHand >= 0)
|
||||
me.SetVirtualItem(0, (uint)mainHand);
|
||||
|
||||
if (offHand >= 0)
|
||||
me.SetVirtualItem(1, (uint)offHand);
|
||||
|
||||
if (ranged >= 0)
|
||||
me.SetVirtualItem(2, (uint)ranged);
|
||||
}
|
||||
|
||||
// Used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims
|
||||
// NOTE: If you use SetCombatMovement while the creature is in combat, it will do NOTHING - This only affects AttackStart
|
||||
// You should make the necessary to make it happen so.
|
||||
// Remember that if you modified _isCombatMovementAllowed (e.g: using SetCombatMovement) it will not be reset at Reset().
|
||||
// It will keep the last value you set.
|
||||
public void SetCombatMovement(bool allowMovement)
|
||||
{
|
||||
_isCombatMovementAllowed = allowMovement;
|
||||
}
|
||||
|
||||
/*bool EnterEvadeIfOutOfCombatArea(uint diff)
|
||||
{
|
||||
if (_evadeCheckCooldown <= diff)
|
||||
_evadeCheckCooldown = 2500;
|
||||
else
|
||||
{
|
||||
_evadeCheckCooldown -= diff;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (me.IsInEvadeMode() || me.GetVictim() == null)
|
||||
return false;
|
||||
|
||||
float x = me.GetPositionX();
|
||||
float y = me.GetPositionY();
|
||||
float z = me.GetPositionZ();
|
||||
|
||||
switch (me.GetEntry())
|
||||
{
|
||||
case 12017: // broodlord (not move down stairs)
|
||||
if (z > 448.60f)
|
||||
return false;
|
||||
break;
|
||||
case 19516: // void reaver (calculate from center of room)
|
||||
if (me.GetDistance2d(432.59f, 371.93f) < 105.0f)
|
||||
return false;
|
||||
break;
|
||||
case 23578: // jan'alai (calculate by Z)
|
||||
if (z > 12.0f)
|
||||
return false;
|
||||
break;
|
||||
case 28860: // sartharion (calculate box)
|
||||
if (x > 3218.86f && x < 3275.69f && y < 572.40f && y > 484.68f)
|
||||
return false;
|
||||
break;
|
||||
default: // For most of creatures that certain area is their home area.
|
||||
Log.outInfo(LogFilter.Server, "Scripts: EnterEvadeIfOutOfCombatArea used for creature entry {0}, but does not have any definition. Using the default one.", me.GetEntry());
|
||||
uint homeAreaId = me.GetMap().GetAreaId(me.GetHomePosition().posX, me.GetHomePosition().posY, me.GetHomePosition().posZ);
|
||||
if (me.GetAreaId() == homeAreaId)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
EnterEvadeMode();
|
||||
return true;
|
||||
}*/
|
||||
|
||||
// Called at any Damage from any attacker (before damage apply)
|
||||
public override void DamageTaken(Unit attacker, ref uint damage) { }
|
||||
|
||||
//Called at creature death
|
||||
public override void JustDied(Unit killer) { }
|
||||
|
||||
//Called at creature killing another unit
|
||||
public override void KilledUnit(Unit victim) { }
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
public override void JustSummoned(Creature summon) { }
|
||||
|
||||
// Called when a summoned creature is despawned
|
||||
public override void SummonedCreatureDespawn(Creature summon) { }
|
||||
|
||||
// Called when hit by a spell
|
||||
public override void SpellHit(Unit caster, SpellInfo spell) { }
|
||||
|
||||
// Called when spell hits a target
|
||||
public override void SpellHitTarget(Unit target, SpellInfo spell) { }
|
||||
|
||||
//Called at waypoint reached or PointMovement end
|
||||
public override void MovementInform(MovementGeneratorType type, uint id) { }
|
||||
|
||||
// Called when AI is temporarily replaced or put back when possess is applied or removed
|
||||
public virtual void OnPossess(bool apply) { }
|
||||
|
||||
public static Creature GetClosestCreatureWithEntry(WorldObject source, uint entry, float maxSearchRange, bool alive = true)
|
||||
{
|
||||
return source.FindNearestCreature(entry, maxSearchRange, alive);
|
||||
}
|
||||
|
||||
public GameObject GetClosestGameObjectWithEntry(WorldObject source, uint entry, float maxSearchRange)
|
||||
{
|
||||
return source.FindNearestGameObject(entry, maxSearchRange);
|
||||
}
|
||||
|
||||
//Called at creature reset either by death or evade
|
||||
public override void Reset() { }
|
||||
|
||||
//Called at creature aggro either by MoveInLOS or Attack Start
|
||||
public override void EnterCombat(Unit victim) { }
|
||||
|
||||
public bool HealthBelowPct(int pct) { return me.HealthBelowPct(pct); }
|
||||
public bool HealthAbovePct(int pct) { return me.HealthAbovePct(pct); }
|
||||
|
||||
public bool IsCombatMovementAllowed() { return _isCombatMovementAllowed; }
|
||||
|
||||
// return true for heroic mode. i.e.
|
||||
// - for dungeon in mode 10-heroic,
|
||||
// - for raid in mode 10-Heroic
|
||||
// - for raid in mode 25-heroic
|
||||
// DO NOT USE to check raid in mode 25-normal.
|
||||
public bool IsHeroic() { return _isHeroic; }
|
||||
|
||||
// return the dungeon or raid difficulty
|
||||
public Difficulty GetDifficulty() { return _difficulty; }
|
||||
|
||||
// return true for 25 man or 25 man heroic mode
|
||||
public bool Is25ManRaid() { return _difficulty == Difficulty.Raid25N || _difficulty == Difficulty.Raid25HC; }
|
||||
|
||||
public T DungeonMode<T>(T normal5, T heroic10)
|
||||
{
|
||||
switch (_difficulty)
|
||||
{
|
||||
case Difficulty.Normal:
|
||||
return normal5;
|
||||
case Difficulty.Heroic:
|
||||
default:
|
||||
return heroic10;
|
||||
}
|
||||
}
|
||||
|
||||
public T RaidMode<T>(T normal10, T normal25)
|
||||
{
|
||||
switch (_difficulty)
|
||||
{
|
||||
case Difficulty.Raid10N:
|
||||
return normal10;
|
||||
case Difficulty.Raid25N:
|
||||
default:
|
||||
return normal25;
|
||||
}
|
||||
}
|
||||
public T RaidMode<T>(T normal10, T normal25, T heroic10, T heroic25)
|
||||
{
|
||||
switch (_difficulty)
|
||||
{
|
||||
case Difficulty.Raid10N:
|
||||
return normal10;
|
||||
case Difficulty.Raid25N:
|
||||
return normal25;
|
||||
case Difficulty.Raid10HC:
|
||||
return heroic10;
|
||||
case Difficulty.Raid25HC:
|
||||
default:
|
||||
return heroic25;
|
||||
}
|
||||
}
|
||||
|
||||
Difficulty _difficulty;
|
||||
bool _isCombatMovementAllowed;
|
||||
bool _isHeroic;
|
||||
}
|
||||
|
||||
public class BossAI : ScriptedAI
|
||||
{
|
||||
public BossAI(Creature creature, uint bossId) : base(creature)
|
||||
{
|
||||
instance = creature.GetInstanceScript();
|
||||
summons = new SummonList(creature);
|
||||
_bossId = bossId;
|
||||
|
||||
if (instance != null)
|
||||
SetBoundary(instance.GetBossBoundary(bossId));
|
||||
|
||||
_scheduler.SetValidator(() =>
|
||||
{
|
||||
return !me.HasUnitState(UnitState.Casting);
|
||||
});
|
||||
}
|
||||
|
||||
public void _Reset()
|
||||
{
|
||||
if (!me.IsAlive())
|
||||
return;
|
||||
|
||||
me.SetCombatPulseDelay(0);
|
||||
me.ResetLootMode();
|
||||
_events.Reset();
|
||||
summons.DespawnAll();
|
||||
_scheduler.CancelAll();
|
||||
if (instance != null)
|
||||
instance.SetBossState(_bossId, EncounterState.NotStarted);
|
||||
}
|
||||
|
||||
public void _JustDied()
|
||||
{
|
||||
_events.Reset();
|
||||
summons.DespawnAll();
|
||||
_scheduler.CancelAll();
|
||||
if (instance != null)
|
||||
instance.SetBossState(_bossId, EncounterState.Done);
|
||||
}
|
||||
|
||||
public void _EnterCombat()
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
// bosses do not respawn, check only on enter combat
|
||||
if (!instance.CheckRequiredBosses(_bossId))
|
||||
{
|
||||
EnterEvadeMode(EvadeReason.SequenceBreak);
|
||||
return;
|
||||
}
|
||||
instance.SetBossState(_bossId, EncounterState.InProgress);
|
||||
}
|
||||
|
||||
me.SetCombatPulseDelay(5);
|
||||
me.setActive(true);
|
||||
DoZoneInCombat();
|
||||
ScheduleTasks();
|
||||
}
|
||||
|
||||
void TeleportCheaters()
|
||||
{
|
||||
float x, y, z;
|
||||
me.GetPosition(out x, out y, out z);
|
||||
|
||||
var threatList = me.GetThreatManager().getThreatList();
|
||||
foreach (var refe in threatList)
|
||||
{
|
||||
Unit target = refe.getTarget();
|
||||
if (target)
|
||||
if (target.IsTypeId(TypeId.Player) && !CheckBoundary(target))
|
||||
target.NearTeleportTo(x, y, z, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustSummoned(Creature summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
if (me.IsInCombat())
|
||||
DoZoneInCombat(summon);
|
||||
}
|
||||
|
||||
public override void SummonedCreatureDespawn(Creature summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
|
||||
|
||||
_events.ExecuteEvents(eventId =>
|
||||
{
|
||||
ExecuteEvent(eventId);
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
});
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public void _DespawnAtEvade(uint delayToRespawn = 30, Creature who = null)
|
||||
{
|
||||
if (delayToRespawn < 2)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "_DespawnAtEvade called with delay of {0} seconds, defaulting to 2.", delayToRespawn);
|
||||
delayToRespawn = 2;
|
||||
}
|
||||
|
||||
if (!who)
|
||||
who = me;
|
||||
|
||||
TempSummon whoSummon = who.ToTempSummon();
|
||||
if (whoSummon)
|
||||
{
|
||||
Log.outWarn(LogFilter.ScriptsAi, "_DespawnAtEvade called on a temporary summon.");
|
||||
whoSummon.UnSummon();
|
||||
return;
|
||||
}
|
||||
|
||||
who.DespawnOrUnsummon(0, TimeSpan.FromSeconds(delayToRespawn));
|
||||
|
||||
if (instance != null && who == me)
|
||||
instance.SetBossState(_bossId, EncounterState.Fail);
|
||||
}
|
||||
|
||||
public virtual void ExecuteEvent(uint eventId) { }
|
||||
|
||||
public virtual void ScheduleTasks() { }
|
||||
|
||||
public override void Reset() { _Reset(); }
|
||||
public override void EnterCombat(Unit who) { _EnterCombat(); }
|
||||
public override void JustDied(Unit killer) { _JustDied(); }
|
||||
public override void JustReachedHome() { _JustReachedHome(); }
|
||||
|
||||
public override bool CanAIAttack(Unit victim) { return CheckBoundary(victim); }
|
||||
|
||||
public void _JustReachedHome() { me.setActive(false); }
|
||||
|
||||
public InstanceScript instance;
|
||||
public SummonList summons;
|
||||
uint _bossId;
|
||||
}
|
||||
|
||||
public class WorldBossAI : ScriptedAI
|
||||
{
|
||||
public WorldBossAI(Creature creature) : base(creature)
|
||||
{
|
||||
summons = new SummonList(creature);
|
||||
}
|
||||
|
||||
void _Reset()
|
||||
{
|
||||
if (!me.IsAlive())
|
||||
return;
|
||||
|
||||
_events.Reset();
|
||||
summons.DespawnAll();
|
||||
}
|
||||
|
||||
void _JustDied()
|
||||
{
|
||||
_events.Reset();
|
||||
summons.DespawnAll();
|
||||
}
|
||||
|
||||
void _EnterCombat()
|
||||
{
|
||||
Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true);
|
||||
if (target)
|
||||
AttackStart(target);
|
||||
}
|
||||
|
||||
public override void JustSummoned(Creature summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
Unit target = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true);
|
||||
if (target)
|
||||
summon.GetAI().AttackStart(target);
|
||||
}
|
||||
|
||||
public override void SummonedCreatureDespawn(Creature summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
|
||||
_events.ExecuteEvents(eventId =>
|
||||
{
|
||||
ExecuteEvent(eventId);
|
||||
|
||||
if (me.HasUnitState(UnitState.Casting))
|
||||
return;
|
||||
});
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
// Hook used to execute events scheduled into EventMap without the need
|
||||
// to override UpdateAI
|
||||
// note: You must re-schedule the event within this method if the event
|
||||
// is supposed to run more than once
|
||||
public virtual void ExecuteEvent(uint eventId) { }
|
||||
|
||||
public override void Reset() { _Reset(); }
|
||||
|
||||
public override void EnterCombat(Unit who) { _EnterCombat(); }
|
||||
|
||||
public override void JustDied(Unit killer) { _JustDied(); }
|
||||
|
||||
SummonList summons;
|
||||
}
|
||||
|
||||
public class SummonList : List<ObjectGuid>
|
||||
{
|
||||
public SummonList(Creature creature)
|
||||
{
|
||||
me = creature;
|
||||
}
|
||||
|
||||
public void Summon(Creature summon) { Add(summon.GetGUID()); }
|
||||
|
||||
public void DoZoneInCombat(uint entry = 0, float maxRangeToNearestTarget = 250.0f)
|
||||
{
|
||||
foreach (var id in this)
|
||||
{
|
||||
Creature summon = ObjectAccessor.GetCreature(me, id);
|
||||
if (summon && summon.IsAIEnabled && (entry == 0 || summon.GetEntry() == entry))
|
||||
{
|
||||
summon.GetAI().DoZoneInCombat(null, maxRangeToNearestTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DespawnEntry(uint entry)
|
||||
{
|
||||
foreach (var id in this)
|
||||
{
|
||||
Creature summon = ObjectAccessor.GetCreature(me, id);
|
||||
if (!summon)
|
||||
Remove(id);
|
||||
else if (summon.GetEntry() == entry)
|
||||
{
|
||||
Remove(id);
|
||||
summon.DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DespawnAll()
|
||||
{
|
||||
while (!this.Empty())
|
||||
{
|
||||
Creature summon = ObjectAccessor.GetCreature(me, this.FirstOrDefault());
|
||||
this.RemoveAt(0);
|
||||
if (summon)
|
||||
summon.DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
|
||||
public void Despawn(Creature summon) { Remove(summon.GetGUID()); }
|
||||
|
||||
public void DespawnIf(Predicate<ObjectGuid> predicate)
|
||||
{
|
||||
RemoveAll(predicate);
|
||||
}
|
||||
|
||||
public void RemoveNotExisting()
|
||||
{
|
||||
foreach (var id in this.ToList())
|
||||
{
|
||||
if (!ObjectAccessor.GetCreature(me, id))
|
||||
Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void DoAction(int info, Predicate<ObjectGuid> predicate, ushort max = 0)
|
||||
{
|
||||
// We need to use a copy of SummonList here, otherwise original SummonList would be modified
|
||||
List<ObjectGuid> listCopy = new List<ObjectGuid>(this);
|
||||
listCopy.RandomResize(predicate, max);
|
||||
DoActionImpl(info, listCopy);
|
||||
}
|
||||
|
||||
public bool HasEntry(uint entry)
|
||||
{
|
||||
foreach (var id in this)
|
||||
{
|
||||
Creature summon = ObjectAccessor.GetCreature(me, id);
|
||||
if (summon && summon.GetEntry() == entry)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DoActionImpl(int action, List<ObjectGuid> summons)
|
||||
{
|
||||
foreach (var guid in summons)
|
||||
{
|
||||
Creature summon = ObjectAccessor.GetCreature(me, guid);
|
||||
if (summon && summon.IsAIEnabled)
|
||||
summon.GetAI().DoAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
Creature me;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class npc_escortAI : ScriptedAI
|
||||
{
|
||||
public npc_escortAI(Creature creature) : base(creature)
|
||||
{
|
||||
m_uiPlayerGUID = ObjectGuid.Empty;
|
||||
m_uiWPWaitTimer = 2500;
|
||||
m_uiPlayerCheckTimer = 1000;
|
||||
m_uiEscortState = eEscortState.None;
|
||||
MaxPlayerDistance = 50;
|
||||
m_pQuestForEscort = null;
|
||||
m_bIsActiveAttacker = true;
|
||||
m_bIsRunning = false;
|
||||
m_bCanInstantRespawn = false;
|
||||
m_bCanReturnToStart = false;
|
||||
DespawnAtEnd = true;
|
||||
DespawnAtFar = true;
|
||||
ScriptWP = false;
|
||||
HasImmuneToNPCFlags = false;
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (me.Attack(who, true))
|
||||
{
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point)
|
||||
me.GetMotionMaster().MovementExpired();
|
||||
|
||||
if (IsCombatMovementAllowed())
|
||||
me.GetMotionMaster().MoveChase(who);
|
||||
}
|
||||
}
|
||||
|
||||
//see followerAI
|
||||
bool AssistPlayerInCombatAgainst(Unit who)
|
||||
{
|
||||
if (!who || !who.GetVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me.IsFriendlyTo(who))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
if (me.IsWithinDistInMap(who, GetMaxPlayerDistance()) && me.IsWithinLOSInMap(who))
|
||||
{
|
||||
//already fighting someone?
|
||||
if (!me.GetVictim())
|
||||
{
|
||||
AttackStart(who);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
who.SetInCombatWith(me);
|
||||
me.AddThreat(who, 0.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void MoveInLineOfSight(Unit who)
|
||||
{
|
||||
if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.isTargetableForAttack() && who.isInAccessiblePlaceFor(me))
|
||||
{
|
||||
if (HasEscortState(eEscortState.Escorting) && AssistPlayerInCombatAgainst(who))
|
||||
return;
|
||||
|
||||
if (!me.CanFly() && me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ)
|
||||
return;
|
||||
|
||||
if (me.IsHostileTo(who))
|
||||
{
|
||||
float fAttackRadius = me.GetAttackDistance(who);
|
||||
if (me.IsWithinDistInMap(who, fAttackRadius) && me.IsWithinLOSInMap(who))
|
||||
{
|
||||
if (!me.GetVictim())
|
||||
{
|
||||
// Clear distracted state on combat
|
||||
if (me.HasUnitState(UnitState.Distracted))
|
||||
{
|
||||
me.ClearUnitState(UnitState.Distracted);
|
||||
me.GetMotionMaster().Clear();
|
||||
}
|
||||
|
||||
AttackStart(who);
|
||||
}
|
||||
else if (me.GetMap().IsDungeon())
|
||||
{
|
||||
who.SetInCombatWith(me);
|
||||
me.AddThreat(who, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
if (!HasEscortState(eEscortState.Escorting) || m_uiPlayerGUID.IsEmpty() || m_pQuestForEscort == null)
|
||||
return;
|
||||
|
||||
Player player = GetPlayerForEscort();
|
||||
if (player)
|
||||
{
|
||||
Group group = player.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
|
||||
{
|
||||
Player member = groupRef.GetSource();
|
||||
if (member)
|
||||
member.FailQuest(m_pQuestForEscort.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
player.FailQuest(m_pQuestForEscort.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustRespawned()
|
||||
{
|
||||
m_uiEscortState = eEscortState.None;
|
||||
|
||||
if (!IsCombatMovementAllowed())
|
||||
SetCombatMovement(true);
|
||||
|
||||
//add a small delay before going to first waypoint, normal in near all cases
|
||||
m_uiWPWaitTimer = 2500;
|
||||
|
||||
if (me.getFaction() != me.GetCreatureTemplate().Faction)
|
||||
me.RestoreFaction();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void ReturnToLastPoint()
|
||||
{
|
||||
float x, y, z, o;
|
||||
me.GetHomePosition(out x, out y, out z, out o);
|
||||
me.GetMotionMaster().MovePoint(0xFFFFFF, x, y, z);
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
|
||||
{
|
||||
me.RemoveAllAuras();
|
||||
me.DeleteThreatList();
|
||||
me.CombatStop(true);
|
||||
me.SetLootRecipient(null);
|
||||
|
||||
if (HasEscortState(eEscortState.Escorting))
|
||||
{
|
||||
AddEscortState(eEscortState.Returning);
|
||||
ReturnToLastPoint();
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has left combat and is now returning to last point");
|
||||
}
|
||||
else
|
||||
{
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
if (HasImmuneToNPCFlags)
|
||||
me.SetFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc);
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPlayerOrGroupInRange()
|
||||
{
|
||||
Player player = GetPlayerForEscort();
|
||||
if (player)
|
||||
{
|
||||
Group group = player.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
|
||||
{
|
||||
Player member = groupRef.GetSource();
|
||||
if (member)
|
||||
if (me.IsWithinDistInMap(member, GetMaxPlayerDistance()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (me.IsWithinDistInMap(player, GetMaxPlayerDistance()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
//Waypoint Updating
|
||||
if (HasEscortState(eEscortState.Escorting) && !me.GetVictim() && m_uiWPWaitTimer != 0 && !HasEscortState(eEscortState.Returning))
|
||||
{
|
||||
if (m_uiWPWaitTimer <= diff)
|
||||
{
|
||||
//End of the line
|
||||
if (WPIndex == WaypointList.Count)
|
||||
{
|
||||
if (DespawnAtEnd)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints");
|
||||
|
||||
if (m_bCanReturnToStart)
|
||||
{
|
||||
float fRetX, fRetY, fRetZ;
|
||||
me.GetRespawnPosition(out fRetX, out fRetY, out fRetZ);
|
||||
|
||||
me.GetMotionMaster().MovePoint(0xFFFFFE, fRetX, fRetY, fRetZ);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI are returning home to spawn location: {0}, {1}, {2}, {3}", 0xFFFFFE, fRetX, fRetY, fRetZ);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bCanInstantRespawn)
|
||||
{
|
||||
me.setDeathState(DeathState.JustDied);
|
||||
me.Respawn();
|
||||
}
|
||||
else
|
||||
me.DespawnOrUnsummon();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints with Despawn off");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasEscortState(eEscortState.Paused))
|
||||
{
|
||||
me.GetMotionMaster().MovePoint(GetCurrentWaypoint().id, GetCurrentWaypoint().x, GetCurrentWaypoint().y, GetCurrentWaypoint().z);
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI start waypoint {0} ({1}, {2}, {3}).", GetCurrentWaypoint().id, GetCurrentWaypoint().x, GetCurrentWaypoint().y, GetCurrentWaypoint().z);
|
||||
|
||||
WaypointStart(GetCurrentWaypoint().id);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_uiWPWaitTimer -= diff;
|
||||
}
|
||||
|
||||
//Check if player or any member of his group is within range
|
||||
if (HasEscortState(eEscortState.Escorting) && !m_uiPlayerGUID.IsEmpty() && !me.GetVictim() && !HasEscortState(eEscortState.Returning))
|
||||
{
|
||||
if (m_uiPlayerCheckTimer <= diff)
|
||||
{
|
||||
if (DespawnAtFar && !IsPlayerOrGroupInRange())
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found");
|
||||
|
||||
if (m_bCanInstantRespawn)
|
||||
{
|
||||
me.setDeathState(DeathState.JustDied);
|
||||
me.Respawn();
|
||||
}
|
||||
else
|
||||
me.DespawnOrUnsummon();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiPlayerCheckTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiPlayerCheckTimer -= diff;
|
||||
}
|
||||
|
||||
UpdateEscortAI(diff);
|
||||
}
|
||||
|
||||
void UpdateEscortAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType moveType, uint pointId)
|
||||
{
|
||||
if (moveType != MovementGeneratorType.Point || !HasEscortState(eEscortState.Escorting))
|
||||
return;
|
||||
|
||||
//Combat start position reached, continue waypoint movement
|
||||
if (pointId == 0xFFFFFF)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original position before combat");
|
||||
|
||||
me.SetWalk(!m_bIsRunning);
|
||||
RemoveEscortState(eEscortState.Returning);
|
||||
|
||||
if (m_uiWPWaitTimer == 0)
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else if (pointId == 0xFFFFFE)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original home location and will continue from beginning of waypoint list.");
|
||||
|
||||
WPIndex = 0;
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Make sure that we are still on the right waypoint
|
||||
if (GetCurrentWaypoint().id != pointId)
|
||||
{
|
||||
Log.outDebug(LogFilter.Server, "TSCR ERROR: EscortAI reached waypoint out of order {0}, expected {1}, creature entry {2}", pointId, GetCurrentWaypoint().id, me.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI Waypoint {0} reached", GetCurrentWaypoint().id);
|
||||
|
||||
//Call WP function
|
||||
WaypointReached(GetCurrentWaypoint().id);
|
||||
|
||||
m_uiWPWaitTimer = GetCurrentWaypoint().WaitTimeMs + 1;
|
||||
|
||||
++WPIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWaypoint(uint id, float x, float y, float z, uint waitTime = 0)
|
||||
{
|
||||
Escort_Waypoint t = new Escort_Waypoint(id, x, y, z, waitTime);
|
||||
|
||||
WaypointList.Add(t);
|
||||
ScriptWP = true;
|
||||
}
|
||||
|
||||
void FillPointMovementListForCreature()
|
||||
{
|
||||
var movePoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry());
|
||||
if (movePoints.Empty())
|
||||
return;
|
||||
|
||||
foreach (var pointMove in movePoints)
|
||||
{
|
||||
Escort_Waypoint point = new Escort_Waypoint(pointMove.uiPointId, pointMove.fX, pointMove.fY, pointMove.fZ, pointMove.uiWaitTime);
|
||||
WaypointList.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRun(bool on = true)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
if (!m_bIsRunning)
|
||||
me.SetWalk(false);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set run mode, but is already running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bIsRunning)
|
||||
me.SetWalk(true);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set walk mode, but is already walking.");
|
||||
}
|
||||
|
||||
m_bIsRunning = on;
|
||||
}
|
||||
|
||||
/// todo get rid of this many variables passed in function.
|
||||
public void Start(bool isActiveAttacker = true, bool run = false, ObjectGuid playerGUID = default(ObjectGuid), Quest quest = null, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true)
|
||||
{
|
||||
if (me.GetVictim())
|
||||
{
|
||||
Log.outError(LogFilter.Server, "TSCR ERROR: EscortAI (script: {0}, creature entry: {1}) attempts to Start while in combat", me.GetScriptName(), me.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(eEscortState.Escorting))
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "EscortAI (script: {0}, creature entry: {1}) attempts to Start while already escorting", me.GetScriptName(), me.GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ScriptWP && resetWaypoints) // sd2 never adds wp in script, but tc does
|
||||
{
|
||||
if (!WaypointList.Empty())
|
||||
WaypointList.Clear();
|
||||
FillPointMovementListForCreature();
|
||||
}
|
||||
|
||||
if (WaypointList.Empty())
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "EscortAI (script: {0}, creature entry: {1}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {2}).",
|
||||
me.GetScriptName(), me.GetEntry(), quest != null ? quest.Id : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_bIsActiveAttacker = isActiveAttacker;
|
||||
m_bIsRunning = run;
|
||||
|
||||
m_uiPlayerGUID = playerGUID;
|
||||
m_pQuestForEscort = quest;
|
||||
|
||||
m_bCanInstantRespawn = instantRespawn;
|
||||
m_bCanReturnToStart = canLoopPath;
|
||||
|
||||
if (m_bCanReturnToStart && m_bCanInstantRespawn)
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");
|
||||
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint)
|
||||
{
|
||||
me.GetMotionMaster().MovementExpired();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
|
||||
}
|
||||
|
||||
//disable npcflags
|
||||
me.SetUInt64Value(UnitFields.NpcFlags, (ulong)NPCFlags.None);
|
||||
if (me.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))
|
||||
{
|
||||
HasImmuneToNPCFlags = true;
|
||||
me.RemoveFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc);
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI started with {0} waypoints. ActiveAttacker = {1}, Run = {2}, PlayerGUID = {3}", WaypointList.Count, m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID);
|
||||
|
||||
WPIndex = 0;
|
||||
|
||||
//Set initial speed
|
||||
if (m_bIsRunning)
|
||||
me.SetWalk(false);
|
||||
else
|
||||
me.SetWalk(true);
|
||||
|
||||
AddEscortState(eEscortState.Escorting);
|
||||
}
|
||||
|
||||
public void SetEscortPaused(bool on)
|
||||
{
|
||||
if (!HasEscortState(eEscortState.Escorting))
|
||||
return;
|
||||
|
||||
if (on)
|
||||
AddEscortState(eEscortState.Paused);
|
||||
else
|
||||
RemoveEscortState(eEscortState.Paused);
|
||||
}
|
||||
|
||||
bool SetNextWaypoint(uint pointId, float x, float y, float z, float orientation)
|
||||
{
|
||||
me.UpdatePosition(x, y, z, orientation);
|
||||
return SetNextWaypoint(pointId, false, true);
|
||||
}
|
||||
|
||||
bool SetNextWaypoint(uint pointId, bool setPosition, bool resetWaypointsOnFail)
|
||||
{
|
||||
if (!WaypointList.Empty())
|
||||
WaypointList.Clear();
|
||||
|
||||
FillPointMovementListForCreature();
|
||||
|
||||
if (WaypointList.Empty())
|
||||
return false;
|
||||
|
||||
int size = WaypointList.Count;
|
||||
Escort_Waypoint waypoint = new Escort_Waypoint(0, 0, 0, 0, 0);
|
||||
do
|
||||
{
|
||||
waypoint = WaypointList.FirstOrDefault();
|
||||
WaypointList.RemoveAt(0);
|
||||
if (waypoint.id == pointId)
|
||||
{
|
||||
if (setPosition)
|
||||
me.UpdatePosition(waypoint.x, waypoint.y, waypoint.z, me.GetOrientation());
|
||||
|
||||
WPIndex = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (!WaypointList.Empty());
|
||||
|
||||
// we failed.
|
||||
// we reset the waypoints in the start; if we pulled any, reset it again
|
||||
if (resetWaypointsOnFail && size != WaypointList.Count)
|
||||
{
|
||||
if (!WaypointList.Empty())
|
||||
WaypointList.Clear();
|
||||
|
||||
FillPointMovementListForCreature();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool GetWaypointPosition(uint pointId, ref float x, ref float y, ref float z)
|
||||
{
|
||||
var waypoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry());
|
||||
if (waypoints.Empty())
|
||||
return false;
|
||||
|
||||
foreach (var point in waypoints)
|
||||
{
|
||||
if (point.uiPointId == pointId)
|
||||
{
|
||||
x = point.fX;
|
||||
y = point.fY;
|
||||
z = point.fZ;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void WaypointReached(uint pointId) { }
|
||||
public virtual void WaypointStart(uint pointId) { }
|
||||
|
||||
public bool HasEscortState(eEscortState escortState) { return m_uiEscortState.HasAnyFlag(escortState); }
|
||||
public override bool IsEscorted() { return m_uiEscortState.HasAnyFlag(eEscortState.Escorting); }
|
||||
|
||||
public void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; }
|
||||
public float GetMaxPlayerDistance() { return MaxPlayerDistance; }
|
||||
|
||||
public void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; }
|
||||
public void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; }
|
||||
public bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override
|
||||
public void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; }
|
||||
public ObjectGuid GetEventStarterGUID() { return m_uiPlayerGUID; }
|
||||
|
||||
public Player GetPlayerForEscort() { return Global.ObjAccessor.GetPlayer(me, m_uiPlayerGUID); }
|
||||
|
||||
void AddEscortState(eEscortState escortState) { m_uiEscortState |= escortState; }
|
||||
void RemoveEscortState(eEscortState escortState) { m_uiEscortState &= ~escortState; }
|
||||
|
||||
ObjectGuid m_uiPlayerGUID;
|
||||
uint m_uiWPWaitTimer;
|
||||
uint m_uiPlayerCheckTimer;
|
||||
eEscortState m_uiEscortState;
|
||||
float MaxPlayerDistance;
|
||||
|
||||
Quest m_pQuestForEscort; //generally passed in Start() when regular escort script.
|
||||
|
||||
List<Escort_Waypoint> WaypointList = new List<Escort_Waypoint>();
|
||||
Escort_Waypoint GetCurrentWaypoint()
|
||||
{
|
||||
return WaypointList[WPIndex];
|
||||
}
|
||||
int WPIndex;
|
||||
|
||||
bool m_bIsActiveAttacker; //obsolete, determined by faction.
|
||||
bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK)
|
||||
bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used)
|
||||
bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests.
|
||||
bool DespawnAtEnd;
|
||||
bool DespawnAtFar;
|
||||
bool ScriptWP;
|
||||
bool HasImmuneToNPCFlags;
|
||||
}
|
||||
|
||||
public class Escort_Waypoint
|
||||
{
|
||||
public Escort_Waypoint(uint _id, float _x, float _y, float _z, uint _w)
|
||||
{
|
||||
id = _id;
|
||||
x = _x;
|
||||
y = _y;
|
||||
z = _z;
|
||||
WaitTimeMs = _w;
|
||||
}
|
||||
|
||||
public uint id;
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public uint WaitTimeMs;
|
||||
}
|
||||
|
||||
public enum eEscortState
|
||||
{
|
||||
None = 0x000, //nothing in progress
|
||||
Escorting = 0x001, //escort are in progress
|
||||
Returning = 0x002, //escort is returning after being in combat
|
||||
Paused = 0x004 //will not proceed with waypoints before state is removed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
enum eFollowState
|
||||
{
|
||||
None = 0x000,
|
||||
Inprogress = 0x001, //must always have this state for any follow
|
||||
Returning = 0x002, //when returning to combat start after being in combat
|
||||
Paused = 0x004, //disables following
|
||||
Complete = 0x008, //follow is completed and may end
|
||||
PreEvent = 0x010, //not implemented (allow pre event to run, before follow is initiated)
|
||||
PostEvent = 0x020 //can be set at complete and allow post event to run
|
||||
}
|
||||
|
||||
class FollowerAI : ScriptedAI
|
||||
{
|
||||
public FollowerAI(Creature creature) : base(creature)
|
||||
{
|
||||
m_uiUpdateFollowTimer = 2500;
|
||||
m_uiFollowState = eFollowState.None;
|
||||
m_pQuestForFollow = null;
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (me.Attack(who, true))
|
||||
{
|
||||
me.AddThreat(who, 0.0f);
|
||||
me.SetInCombatWith(who);
|
||||
who.SetInCombatWith(me);
|
||||
|
||||
if (me.HasUnitState(UnitState.Follow))
|
||||
me.ClearUnitState(UnitState.Follow);
|
||||
|
||||
if (IsCombatMovementAllowed())
|
||||
me.GetMotionMaster().MoveChase(who);
|
||||
}
|
||||
}
|
||||
|
||||
//This part provides assistance to a player that are attacked by who, even if out of normal aggro range
|
||||
//It will cause me to attack who that are attacking _any_ player (which has been confirmed may happen also on offi)
|
||||
//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
|
||||
bool AssistPlayerInCombatAgainst(Unit who)
|
||||
{
|
||||
if (!who || !who.GetVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me.IsFriendlyTo(who))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
if (me.IsWithinDistInMap(who, 100.0f) && me.IsWithinLOSInMap(who))
|
||||
{
|
||||
//already fighting someone?
|
||||
if (!me.GetVictim())
|
||||
{
|
||||
AttackStart(who);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
who.SetInCombatWith(me);
|
||||
me.AddThreat(who, 0.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void MoveInLineOfSight(Unit who)
|
||||
{
|
||||
if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.isTargetableForAttack() && who.isInAccessiblePlaceFor(me))
|
||||
{
|
||||
if (HasFollowState(eFollowState.Inprogress) && AssistPlayerInCombatAgainst(who))
|
||||
return;
|
||||
|
||||
if (!me.CanFly() && me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ)
|
||||
return;
|
||||
|
||||
if (me.IsHostileTo(who))
|
||||
{
|
||||
float fAttackRadius = me.GetAttackDistance(who);
|
||||
if (me.IsWithinDistInMap(who, fAttackRadius) && me.IsWithinLOSInMap(who))
|
||||
{
|
||||
if (!me.GetVictim())
|
||||
{
|
||||
// Clear distracted state on combat
|
||||
if (me.HasUnitState(UnitState.Distracted))
|
||||
{
|
||||
me.ClearUnitState(UnitState.Distracted);
|
||||
me.GetMotionMaster().Clear();
|
||||
}
|
||||
|
||||
AttackStart(who);
|
||||
}
|
||||
else if (me.GetMap().IsDungeon())
|
||||
{
|
||||
who.SetInCombatWith(me);
|
||||
me.AddThreat(who, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
if (!HasFollowState(eFollowState.Inprogress) || m_uiLeaderGUID.IsEmpty() || m_pQuestForFollow == null)
|
||||
return;
|
||||
|
||||
/// @todo need a better check for quests with time limit.
|
||||
Player player = GetLeaderForFollower();
|
||||
if (player)
|
||||
{
|
||||
Group group = player.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
|
||||
{
|
||||
Player member = groupRef.GetSource();
|
||||
if (member)
|
||||
member.FailQuest(m_pQuestForFollow.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
player.FailQuest(m_pQuestForFollow.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustRespawned()
|
||||
{
|
||||
m_uiFollowState = eFollowState.None;
|
||||
|
||||
if (!IsCombatMovementAllowed())
|
||||
SetCombatMovement(true);
|
||||
|
||||
if (me.getFaction() != me.GetCreatureTemplate().Faction)
|
||||
me.SetFaction(me.GetCreatureTemplate().Faction);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why)
|
||||
{
|
||||
me.RemoveAllAuras();
|
||||
me.DeleteThreatList();
|
||||
me.CombatStop(true);
|
||||
me.SetLootRecipient(null);
|
||||
|
||||
if (HasFollowState(eFollowState.Inprogress))
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI left combat, returning to CombatStartPosition.");
|
||||
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase)
|
||||
{
|
||||
float fPosX, fPosY, fPosZ;
|
||||
me.GetPosition(out fPosX, out fPosY, out fPosZ);
|
||||
me.GetMotionMaster().MovePoint(0xFFFFFF, fPosX, fPosY, fPosZ);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Chase)
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override void UpdateAI(uint uiDiff)
|
||||
{
|
||||
if (HasFollowState(eFollowState.Inprogress) && !me.GetVictim())
|
||||
{
|
||||
if (m_uiUpdateFollowTimer <= uiDiff)
|
||||
{
|
||||
if (HasFollowState(eFollowState.Complete) && !HasFollowState(eFollowState.PostEvent))
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI is set completed, despawns.");
|
||||
me.DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
bool bIsMaxRangeExceeded = true;
|
||||
|
||||
Player player = GetLeaderForFollower();
|
||||
if (player)
|
||||
{
|
||||
if (HasFollowState(eFollowState.Returning))
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI is returning to leader.");
|
||||
|
||||
RemoveFollowState(eFollowState.Returning);
|
||||
me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
|
||||
return;
|
||||
}
|
||||
|
||||
Group group = player.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
|
||||
{
|
||||
Player member = groupRef.GetSource();
|
||||
if (member && me.IsWithinDistInMap(member, 100.0f))
|
||||
{
|
||||
bIsMaxRangeExceeded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me.IsWithinDistInMap(player, 100.0f))
|
||||
bIsMaxRangeExceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsMaxRangeExceeded)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI failed because player/group was to far away or not found");
|
||||
me.DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiUpdateFollowTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiUpdateFollowTimer -= uiDiff;
|
||||
}
|
||||
|
||||
UpdateFollowerAI(uiDiff);
|
||||
}
|
||||
|
||||
void UpdateFollowerAI(uint diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType motionType, uint pointId)
|
||||
{
|
||||
if (motionType != MovementGeneratorType.Point || !HasFollowState(eFollowState.Inprogress))
|
||||
return;
|
||||
|
||||
if (pointId == 0xFFFFFF)
|
||||
{
|
||||
if (GetLeaderForFollower())
|
||||
{
|
||||
if (!HasFollowState(eFollowState.Paused))
|
||||
AddFollowState(eFollowState.Returning);
|
||||
}
|
||||
else
|
||||
me.DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
|
||||
void StartFollow(Player player, uint factionForFollower = 0, Quest quest = null)
|
||||
{
|
||||
if (me.GetVictim())
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI attempt to StartFollow while in combat.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasFollowState(eFollowState.Inprogress))
|
||||
{
|
||||
Log.outError(LogFilter.Scenario, "FollowerAI attempt to StartFollow while already following.");
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_uiLeaderGUID = player.GetGUID();
|
||||
|
||||
if (factionForFollower != 0)
|
||||
me.SetFaction(factionForFollower);
|
||||
|
||||
m_pQuestForFollow = quest;
|
||||
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint)
|
||||
{
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
|
||||
}
|
||||
|
||||
me.SetUInt64Value(UnitFields.NpcFlags, (uint)NPCFlags.None);
|
||||
|
||||
AddFollowState(eFollowState.Inprogress);
|
||||
|
||||
me.GetMotionMaster().MoveFollow(player, SharedConst.PetFollowDist, SharedConst.PetFollowAngle);
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI start follow {0} ({1})", player.GetName(), m_uiLeaderGUID.ToString());
|
||||
}
|
||||
|
||||
Player GetLeaderForFollower()
|
||||
{
|
||||
Player player = Global.ObjAccessor.GetPlayer(me, m_uiLeaderGUID);
|
||||
if (player)
|
||||
{
|
||||
if (player.IsAlive())
|
||||
return player;
|
||||
else
|
||||
{
|
||||
Group group = player.GetGroup();
|
||||
if (group)
|
||||
{
|
||||
for (GroupReference groupRef = group.GetFirstMember(); groupRef != null; groupRef = groupRef.next())
|
||||
{
|
||||
Player member = groupRef.GetSource();
|
||||
|
||||
if (member && member.IsAlive() && me.IsWithinDistInMap(member, 100.0f))
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader changed and returned new leader.");
|
||||
m_uiLeaderGUID = member.GetGUID();
|
||||
return member;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "FollowerAI GetLeader can not find suitable leader.");
|
||||
return null;
|
||||
}
|
||||
|
||||
void SetFollowComplete(bool bWithEndEvent = false)
|
||||
{
|
||||
if (me.HasUnitState(UnitState.Follow))
|
||||
{
|
||||
me.ClearUnitState(UnitState.Follow);
|
||||
|
||||
me.StopMoving();
|
||||
me.GetMotionMaster().Clear();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
|
||||
if (bWithEndEvent)
|
||||
AddFollowState(eFollowState.PostEvent);
|
||||
else
|
||||
{
|
||||
if (HasFollowState(eFollowState.PostEvent))
|
||||
RemoveFollowState(eFollowState.PostEvent);
|
||||
}
|
||||
|
||||
AddFollowState(eFollowState.Complete);
|
||||
}
|
||||
|
||||
bool HasFollowState(eFollowState uiFollowState) { return (m_uiFollowState & uiFollowState) != 0; }
|
||||
|
||||
void AddFollowState(eFollowState uiFollowState) { m_uiFollowState |= uiFollowState; }
|
||||
void RemoveFollowState(eFollowState uiFollowState) { m_uiFollowState &= ~uiFollowState; }
|
||||
|
||||
ObjectGuid m_uiLeaderGUID;
|
||||
uint m_uiUpdateFollowTimer;
|
||||
eFollowState m_uiFollowState;
|
||||
|
||||
Quest m_pQuestForFollow; //normally we have a quest
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user