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:
Fabian
2017-10-26 17:23:44 +02:00
parent 227702e19c
commit a3dc7b3f48
844 changed files with 26064 additions and 1824 deletions
+129
View File
@@ -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);
}
}
}
+60
View File
@@ -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) { }
}
}
+361
View File
@@ -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();
}
}
}
+499
View File
@@ -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
}
}
+69
View File
@@ -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) { }
}
}
+76
View File
@@ -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);
}
}
}
+134
View File
@@ -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());
}
}
}
+644
View File
@@ -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;
}
}
+83
View File
@@ -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;
}
}
+575
View File
@@ -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
+803
View File
@@ -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