Core/Movement: waypoint movement
Port From (https://github.com/TrinityCore/TrinityCore/commit/97585597f0b1aff93873fe4d757556731bc0c1b2)
This commit is contained in:
@@ -526,6 +526,10 @@ namespace Game.AI
|
||||
// Called when the dialog status between a player and the creature is requested.
|
||||
public virtual QuestGiverStatus GetDialogStatus(Player player) { return QuestGiverStatus.ScriptedNoStatus; }
|
||||
|
||||
public virtual void WaypointStarted(uint nodeId, uint pathId) { }
|
||||
|
||||
public virtual void WaypointReached(uint nodeId, uint pathId) { }
|
||||
|
||||
public AISpellInfoType GetAISpellInfo(uint spellId, Difficulty difficulty)
|
||||
{
|
||||
return AISpellInfo.LookupByKey((spellId, difficulty));
|
||||
|
||||
@@ -19,30 +19,27 @@ using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using Game.Movement;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.AI
|
||||
{
|
||||
public class NpcEscortAI : ScriptedAI
|
||||
public class EscortAI : ScriptedAI
|
||||
{
|
||||
public NpcEscortAI(Creature creature) : base(creature)
|
||||
public EscortAI(Creature creature) : base(creature)
|
||||
{
|
||||
m_uiPlayerGUID = ObjectGuid.Empty;
|
||||
m_uiWPWaitTimer = 2500;
|
||||
m_uiPlayerCheckTimer = 1000;
|
||||
m_uiEscortState = EscortState.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;
|
||||
_pauseTimer = 2500;
|
||||
_playerCheckTimer = 1000;
|
||||
_maxPlayerDistance = 50;
|
||||
_activeAttacker = true;
|
||||
_despawnAtEnd = true;
|
||||
_despawnAtFar = true;
|
||||
}
|
||||
|
||||
public Player GetPlayerForEscort()
|
||||
{
|
||||
return Global.ObjAccessor.GetPlayer(me, _playerGUID);
|
||||
}
|
||||
|
||||
public override void AttackStart(Unit target)
|
||||
@@ -66,6 +63,9 @@ namespace Game.AI
|
||||
if (!who || !who.GetVictim())
|
||||
return false;
|
||||
|
||||
if (me.HasReactState(ReactStates.Passive))
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist))
|
||||
return false;
|
||||
@@ -75,50 +75,33 @@ namespace Game.AI
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me.IsFriendlyTo(who))
|
||||
if (me.IsValidAssistTarget(who.GetVictim()))
|
||||
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
|
||||
{
|
||||
me.EngageWithTarget(who);
|
||||
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 (who == null)
|
||||
return;
|
||||
|
||||
if (HasEscortState(EscortState.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))
|
||||
me.EngageWithTarget(who);
|
||||
}
|
||||
}
|
||||
base.MoveInLineOfSight(who);
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
if (!HasEscortState(EscortState.Escorting) || m_uiPlayerGUID.IsEmpty() || m_pQuestForEscort == null)
|
||||
if (!HasEscortState(EscortState.Escorting) || _playerGUID.IsEmpty() || _escortQuest == null)
|
||||
return;
|
||||
|
||||
Player player = GetPlayerForEscort();
|
||||
@@ -132,23 +115,23 @@ namespace Game.AI
|
||||
Player member = groupRef.GetSource();
|
||||
if (member)
|
||||
if (member.IsInMap(player))
|
||||
member.FailQuest(m_pQuestForEscort.Id);
|
||||
member.FailQuest(_escortQuest.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
player.FailQuest(m_pQuestForEscort.Id);
|
||||
player.FailQuest(_escortQuest.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public override void JustAppeared()
|
||||
{
|
||||
m_uiEscortState = EscortState.None;
|
||||
_escortState = EscortState.None;
|
||||
|
||||
if (!IsCombatMovementAllowed())
|
||||
SetCombatMovement(true);
|
||||
|
||||
//add a small delay before going to first waypoint, normal in near all cases
|
||||
m_uiWPWaitTimer = 2500;
|
||||
_pauseTimer = 2000;
|
||||
|
||||
if (me.GetFaction() != me.GetCreatureTemplate().Faction)
|
||||
me.RestoreFaction();
|
||||
@@ -172,12 +155,12 @@ namespace Game.AI
|
||||
{
|
||||
AddEscortState(EscortState.Returning);
|
||||
ReturnToLastPoint();
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has left combat and is now returning to last point");
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI.EnterEvadeMode has left combat and is now returning to last point");
|
||||
}
|
||||
else
|
||||
{
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
if (HasImmuneToNPCFlags)
|
||||
if (_hasImmuneToNPCFlags)
|
||||
me.AddUnitFlag(UnitFlags.ImmuneToNpc);
|
||||
Reset();
|
||||
}
|
||||
@@ -209,87 +192,78 @@ namespace Game.AI
|
||||
public override void UpdateAI(uint diff)
|
||||
{
|
||||
//Waypoint Updating
|
||||
if (HasEscortState(EscortState.Escorting) && !me.GetVictim() && m_uiWPWaitTimer != 0 && !HasEscortState(EscortState.Returning))
|
||||
if (HasEscortState(EscortState.Escorting) && !me.IsEngaged() && !HasEscortState(EscortState.Returning))
|
||||
{
|
||||
if (m_uiWPWaitTimer <= diff)
|
||||
if (_pauseTimer <= diff)
|
||||
{
|
||||
//End of the line
|
||||
if (WaypointList[CurrentWPIndex] == null)
|
||||
if (!HasEscortState(EscortState.Paused))
|
||||
{
|
||||
m_uiWPWaitTimer = 0;
|
||||
_pauseTimer = 0;
|
||||
|
||||
if (DespawnAtEnd)
|
||||
if (_ended)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints");
|
||||
_ended = false;
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
|
||||
if (m_bCanReturnToStart)
|
||||
if (_despawnAtEnd)
|
||||
{
|
||||
float fRetX, fRetY, fRetZ;
|
||||
me.GetRespawnPosition(out fRetX, out fRetY, out fRetZ);
|
||||
|
||||
me.GetMotionMaster().MovePoint(EscortPointIds.Home, fRetX, fRetY, fRetZ);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI are returning home to spawn location: {EscortPointIds.Home}, {fRetX}, {fRetY}, {fRetZ}");
|
||||
return;
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI.UpdateAI: reached end of waypoints, despawning at end");
|
||||
if (_returnToStart)
|
||||
{
|
||||
Position respawnPosition = new Position();
|
||||
float orientation;
|
||||
me.GetRespawnPosition(out respawnPosition.posX, out respawnPosition.posY, out respawnPosition.posZ, out orientation);
|
||||
respawnPosition.SetOrientation(orientation);
|
||||
me.GetMotionMaster().MovePoint(EscortPointIds.Home, respawnPosition);
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI.UpdateAI: returning to spawn location: {respawnPosition}");
|
||||
}
|
||||
|
||||
if (m_bCanInstantRespawn && !WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc))
|
||||
{
|
||||
me.SetDeathState(DeathState.JustDied);
|
||||
else if (_instantRespawn)
|
||||
me.Respawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc))
|
||||
me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true);
|
||||
me.DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI.UpdateAI: reached end of waypoints");
|
||||
RemoveEscortState(EscortState.Escorting);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_started)
|
||||
{
|
||||
_started = true;
|
||||
me.GetMotionMaster().MovePath(_path, false);
|
||||
}
|
||||
else if (_resume)
|
||||
{
|
||||
_resume = false;
|
||||
IMovementGenerator movementGenerator = me.GetMotionMaster().GetMotionSlot(MovementSlot.Idle);
|
||||
if (movementGenerator != null)
|
||||
movementGenerator.Resume(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints with Despawn off");
|
||||
return;
|
||||
_pauseTimer -= diff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!HasEscortState(EscortState.Paused))
|
||||
{
|
||||
var currentWp = WaypointList[CurrentWPIndex];
|
||||
me.GetMotionMaster().MovePoint(currentWp.Id, currentWp.X, currentWp.Y, currentWp.Z);
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI start waypoint {currentWp.Id} ({currentWp.X}, {currentWp.Y}, {currentWp.Z}).");
|
||||
|
||||
WaypointStart(currentWp.Id);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if player or any member of his group is within range
|
||||
if (HasEscortState(EscortState.Escorting) && !m_uiPlayerGUID.IsEmpty() && !me.GetVictim() && !HasEscortState(EscortState.Returning))
|
||||
if (_despawnAtFar && HasEscortState(EscortState.Escorting) && !_playerGUID.IsEmpty() && !me.GetVictim() && !HasEscortState(EscortState.Returning))
|
||||
{
|
||||
if (m_uiPlayerCheckTimer <= diff)
|
||||
if (_playerCheckTimer <= diff)
|
||||
{
|
||||
if (DespawnAtFar && !IsPlayerOrGroupInRange())
|
||||
if (!IsPlayerOrGroupInRange())
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI failed because player/group was to far away or not found");
|
||||
|
||||
bool isEscort = false;
|
||||
CreatureData cdata = me.GetCreatureData();
|
||||
if (cdata != null)
|
||||
isEscort = (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && cdata.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc));
|
||||
CreatureData creatureData = me.GetCreatureData();
|
||||
if (creatureData != null)
|
||||
isEscort = (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc) && creatureData.spawnGroupData.flags.HasAnyFlag(SpawnGroupFlags.EscortQuestNpc));
|
||||
|
||||
if (m_bCanInstantRespawn && !isEscort)
|
||||
{
|
||||
me.SetDeathState(DeathState.JustDied);
|
||||
me.Respawn();
|
||||
}
|
||||
else if (m_bCanInstantRespawn && isEscort)
|
||||
if (_instantRespawn && !isEscort)
|
||||
me.DespawnOrUnsummon(0, TimeSpan.FromSeconds(1));
|
||||
else if (_instantRespawn && isEscort)
|
||||
me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true);
|
||||
else
|
||||
me.DespawnOrUnsummon();
|
||||
@@ -297,10 +271,10 @@ namespace Game.AI
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiPlayerCheckTimer = 1000;
|
||||
_playerCheckTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiPlayerCheckTimer -= diff;
|
||||
_playerCheckTimer -= diff;
|
||||
}
|
||||
|
||||
UpdateEscortAI(diff);
|
||||
@@ -314,90 +288,94 @@ namespace Game.AI
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType moveType, uint pointId)
|
||||
public override void MovementInform(MovementGeneratorType moveType, uint Id)
|
||||
{
|
||||
if (moveType != MovementGeneratorType.Point || !HasEscortState(EscortState.Escorting))
|
||||
// no action allowed if there is no escort
|
||||
if (!HasEscortState(EscortState.Escorting))
|
||||
return;
|
||||
|
||||
//Combat start position reached, continue waypoint movement
|
||||
if (pointId == EscortPointIds.LastPoint)
|
||||
if (moveType == MovementGeneratorType.Point)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original position before combat");
|
||||
if (_pauseTimer == 0)
|
||||
_pauseTimer = 2000;
|
||||
|
||||
me.SetWalk(!m_bIsRunning);
|
||||
if (Id == EscortPointIds.LastPoint)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform has returned to original position before combat");
|
||||
|
||||
me.SetWalk(!_running);
|
||||
RemoveEscortState(EscortState.Returning);
|
||||
|
||||
if (m_uiWPWaitTimer == 0)
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else if (pointId == EscortPointIds.Home)
|
||||
else if (Id == EscortPointIds.Home)
|
||||
{
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI has returned to original home location and will continue from beginning of waypoint list.");
|
||||
|
||||
CurrentWPIndex = 0;
|
||||
m_uiWPWaitTimer = 1;
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform: returned to home location and restarting waypoint path");
|
||||
_started = false;
|
||||
}
|
||||
else
|
||||
}
|
||||
else if (moveType == MovementGeneratorType.Waypoint)
|
||||
{
|
||||
var currentWp = WaypointList[CurrentWPIndex];
|
||||
//Make sure that we are still on the right waypoint
|
||||
if (currentWp.Id != pointId)
|
||||
Cypher.Assert(Id < _path.nodes.Count, $"EscortAI.MovementInform: referenced movement id ({Id}) points to non-existing node in loaded path");
|
||||
WaypointNode waypoint = _path.nodes[(int)Id];
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI.MovementInform: waypoint node {waypoint.id} reached");
|
||||
|
||||
// last point
|
||||
if (Id == _path.nodes.Count - 1)
|
||||
{
|
||||
Log.outError(LogFilter.Misc, $"TSCR ERROR: EscortAI reached waypoint out of order {pointId}, expected {currentWp.Id}, creature entry {me.GetEntry()}");
|
||||
return;
|
||||
_started = false;
|
||||
_ended = true;
|
||||
_pauseTimer = 1000;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI Waypoint {currentWp.Id} reached");
|
||||
|
||||
//Call WP function
|
||||
WaypointReached(currentWp.Id);
|
||||
|
||||
m_uiWPWaitTimer = currentWp.WaitTimeMs + 1;
|
||||
|
||||
++CurrentWPIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWaypoint(uint id, float x, float y, float z, uint waitTime = 0)
|
||||
public void AddWaypoint(uint id, float x, float y, float z, float orientation = 0, uint waitTime = 0)
|
||||
{
|
||||
Escort_Waypoint t = new Escort_Waypoint(id, x, y, z, waitTime);
|
||||
GridDefines.NormalizeMapCoord(ref x);
|
||||
GridDefines.NormalizeMapCoord(ref y);
|
||||
|
||||
WaypointList.Add(t);
|
||||
WaypointNode waypoint = new WaypointNode();
|
||||
waypoint.id = id;
|
||||
waypoint.x = x;
|
||||
waypoint.y = y;
|
||||
waypoint.z = z;
|
||||
waypoint.orientation = orientation;
|
||||
waypoint.moveType = _running ? WaypointMoveType.Run : WaypointMoveType.Walk;
|
||||
waypoint.delay = waitTime;
|
||||
waypoint.eventId = 0;
|
||||
waypoint.eventChance = 100;
|
||||
_path.nodes.Add(waypoint);
|
||||
|
||||
ScriptWP = true;
|
||||
_manualPath = true;
|
||||
}
|
||||
|
||||
void FillPointMovementListForCreature()
|
||||
{
|
||||
var movePoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry());
|
||||
if (movePoints.Empty())
|
||||
WaypointPath path = Global.WaypointMgr.GetPath(me.GetEntry());
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
foreach (var point in movePoints)
|
||||
foreach (WaypointNode value in path.nodes)
|
||||
{
|
||||
Escort_Waypoint wayPoint = new Escort_Waypoint(point.uiPointId, point.fX, point.fY, point.fZ, point.uiWaitTime);
|
||||
WaypointList.Add(wayPoint);
|
||||
WaypointNode node = value;
|
||||
GridDefines.NormalizeMapCoord(ref node.x);
|
||||
GridDefines.NormalizeMapCoord(ref node.y);
|
||||
node.moveType = _running ? WaypointMoveType.Run : WaypointMoveType.Walk;
|
||||
|
||||
_path.nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRun(bool on = true)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
if (!m_bIsRunning)
|
||||
if (on && !_running)
|
||||
me.SetWalk(false);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set run mode, but is already running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bIsRunning)
|
||||
else if (!on && _running)
|
||||
me.SetWalk(true);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set walk mode, but is already walking.");
|
||||
}
|
||||
|
||||
m_bIsRunning = on;
|
||||
_running = on;
|
||||
}
|
||||
|
||||
/// todo get rid of this many variables passed in function.
|
||||
@@ -424,69 +402,54 @@ namespace Game.AI
|
||||
|
||||
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());
|
||||
Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) attempts to Start while in combat");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(EscortState.Escorting))
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, "EscortAI (script: {0}, creature entry: {1}) attempts to Start while already escorting", me.GetScriptName(), me.GetEntry());
|
||||
Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) attempts to Start while already escorting");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ScriptWP && resetWaypoints)
|
||||
{
|
||||
if (!WaypointList.Empty())
|
||||
WaypointList.Clear();
|
||||
if (!_manualPath && resetWaypoints)
|
||||
FillPointMovementListForCreature();
|
||||
}
|
||||
|
||||
|
||||
if (WaypointList.Empty())
|
||||
if (_path.nodes.Empty())
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"EscortAI (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {(quest != null ? quest.Id : 0)}).");
|
||||
Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: {(quest != null ? quest.Id : 0)}).");
|
||||
return;
|
||||
}
|
||||
|
||||
// set variables
|
||||
m_bIsActiveAttacker = isActiveAttacker;
|
||||
m_bIsRunning = run;
|
||||
_activeAttacker = isActiveAttacker;
|
||||
_running = run;
|
||||
_playerGUID = playerGUID;
|
||||
_escortQuest = quest;
|
||||
_instantRespawn = instantRespawn;
|
||||
_returnToStart = canLoopPath;
|
||||
|
||||
m_uiPlayerGUID = playerGUID;
|
||||
m_pQuestForEscort = quest;
|
||||
if (_returnToStart && _instantRespawn)
|
||||
Log.outError(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");
|
||||
|
||||
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.");
|
||||
}
|
||||
me.GetMotionMaster().Clear(MovementSlot.Active);
|
||||
|
||||
//disable npcflags
|
||||
me.SetNpcFlags(NPCFlags.None);
|
||||
me.SetNpcFlags2(NPCFlags2.None);
|
||||
if (me.HasUnitFlag(UnitFlags.ImmuneToNpc))
|
||||
{
|
||||
HasImmuneToNPCFlags = true;
|
||||
_hasImmuneToNPCFlags = true;
|
||||
me.RemoveUnitFlag(UnitFlags.ImmuneToNpc);
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI started. ActiveAttacker = {m_bIsActiveAttacker}, Run = {m_bIsRunning}, PlayerGUID = {m_uiPlayerGUID}");
|
||||
Log.outDebug(LogFilter.Scripts, $"EscortAI.Start: (script: {me.GetScriptName()}, creature entry: {me.GetEntry()}) started with {_path.nodes.Count} waypoints. ActiveAttacker = {_activeAttacker}, Run = {_running}, Player = {_playerGUID}");
|
||||
|
||||
CurrentWPIndex = 0;
|
||||
|
||||
//Set initial speed
|
||||
if (m_bIsRunning)
|
||||
me.SetWalk(false);
|
||||
else
|
||||
me.SetWalk(true);
|
||||
// set initial speed
|
||||
me.SetWalk(!_running);
|
||||
|
||||
_started = false;
|
||||
AddEscortState(EscortState.Escorting);
|
||||
}
|
||||
|
||||
@@ -496,75 +459,17 @@ namespace Game.AI
|
||||
return;
|
||||
|
||||
if (on)
|
||||
{
|
||||
AddEscortState(EscortState.Paused);
|
||||
IMovementGenerator movementGenerator = me.GetMotionMaster().GetMotionSlot(MovementSlot.Idle);
|
||||
if (movementGenerator != null)
|
||||
movementGenerator.Pause(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveEscortState(EscortState.Paused);
|
||||
_resume = true;
|
||||
}
|
||||
|
||||
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;
|
||||
do
|
||||
{
|
||||
waypoint = WaypointList.First();
|
||||
WaypointList.RemoveAt(0);
|
||||
if (waypoint.Id == pointId)
|
||||
{
|
||||
if (setPosition)
|
||||
me.UpdatePosition(waypoint.X, waypoint.Y, waypoint.Z, me.GetOrientation());
|
||||
|
||||
CurrentWPIndex = 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;
|
||||
}
|
||||
|
||||
bool GetWaypointPosition(uint pointId, ref float x, ref float y, ref float z)
|
||||
{
|
||||
var waypoints = Global.ScriptMgr.GetPointMoveList(me.GetEntry());
|
||||
if (waypoints == null)
|
||||
return false;
|
||||
|
||||
foreach (var pointMove in waypoints)
|
||||
{
|
||||
if (pointMove.uiPointId == pointId)
|
||||
{
|
||||
x = pointMove.fX;
|
||||
y = pointMove.fY;
|
||||
z = pointMove.fZ;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsEscortNPC(bool onlyIfActive)
|
||||
@@ -578,71 +483,54 @@ namespace Game.AI
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void WaypointReached(uint pointId) { }
|
||||
public virtual void WaypointStart(uint pointId) { }
|
||||
void SetPauseTimer(uint Timer) { _pauseTimer = Timer; }
|
||||
|
||||
public bool HasEscortState(EscortState escortState) { return m_uiEscortState.HasAnyFlag(escortState); }
|
||||
public override bool IsEscorted() { return m_uiEscortState.HasAnyFlag(EscortState.Escorting); }
|
||||
public bool HasEscortState(EscortState escortState) { return (_escortState & escortState) != 0; }
|
||||
public override bool IsEscorted() { return _escortState.HasAnyFlag(EscortState.Escorting); }
|
||||
|
||||
public void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; }
|
||||
public float GetMaxPlayerDistance() { return MaxPlayerDistance; }
|
||||
void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; }
|
||||
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 void SetDespawnAtEnd(bool despawn) { _despawnAtEnd = despawn; }
|
||||
public void SetDespawnAtFar(bool despawn) { _despawnAtFar = despawn; }
|
||||
|
||||
public Player GetPlayerForEscort() { return Global.ObjAccessor.GetPlayer(me, m_uiPlayerGUID); }
|
||||
bool GetAttack() { return _activeAttacker; } // used in EnterEvadeMode override
|
||||
void SetCanAttack(bool attack) { _activeAttacker = attack; }
|
||||
|
||||
void AddEscortState(EscortState escortState) { m_uiEscortState |= escortState; }
|
||||
void RemoveEscortState(EscortState escortState) { m_uiEscortState &= ~escortState; }
|
||||
ObjectGuid GetEventStarterGUID() { return _playerGUID; }
|
||||
|
||||
ObjectGuid m_uiPlayerGUID;
|
||||
uint m_uiWPWaitTimer;
|
||||
uint m_uiPlayerCheckTimer;
|
||||
EscortState m_uiEscortState;
|
||||
float MaxPlayerDistance;
|
||||
void AddEscortState(EscortState escortState) { _escortState |= escortState; }
|
||||
void RemoveEscortState(EscortState escortState) { _escortState &= ~escortState; }
|
||||
|
||||
Quest m_pQuestForEscort; //generally passed in Start() when regular escort script.
|
||||
ObjectGuid _playerGUID;
|
||||
uint _pauseTimer;
|
||||
uint _playerCheckTimer;
|
||||
EscortState _escortState;
|
||||
float _maxPlayerDistance;
|
||||
|
||||
List<Escort_Waypoint> WaypointList = new List<Escort_Waypoint>();
|
||||
int CurrentWPIndex;
|
||||
Quest _escortQuest; //generally passed in Start() when regular escort script.
|
||||
|
||||
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;
|
||||
WaypointPath _path;
|
||||
|
||||
bool _activeAttacker; // obsolete, determined by faction.
|
||||
bool _running; // all creatures are walking by default (has flag MOVEMENTFLAG_WALK)
|
||||
bool _instantRespawn; // if creature should respawn instantly after escort over (if not, database respawntime are used)
|
||||
bool _returnToStart; // if creature can walk same path (loop) without despawn. Not for regular escort quests.
|
||||
bool _despawnAtEnd;
|
||||
bool _despawnAtFar;
|
||||
bool _manualPath;
|
||||
bool _hasImmuneToNPCFlags;
|
||||
bool _started;
|
||||
bool _ended;
|
||||
bool _resume;
|
||||
}
|
||||
|
||||
public enum EscortState
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
None = 0x00, //nothing in progress
|
||||
Escorting = 0x01, //escort are in progress
|
||||
Returning = 0x02, //escort is returning after being in combat
|
||||
Paused = 0x04 //will not proceed with waypoints before state is removed
|
||||
}
|
||||
|
||||
struct EscortPointIds
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -32,18 +33,11 @@ namespace Game.AI
|
||||
|
||||
public SmartAI(Creature creature) : base(creature)
|
||||
{
|
||||
// Spawn in run mode
|
||||
me.SetWalk(false);
|
||||
_escortInvokerCheckTimer = 1000;
|
||||
mRun = true;
|
||||
|
||||
mLastOOCPos = me.GetPosition();
|
||||
|
||||
mCanAutoAttack = true;
|
||||
mCanCombatMove = true;
|
||||
|
||||
mEscortInvokerCheckTimer = 1000;
|
||||
mFollowGuid = ObjectGuid.Empty;
|
||||
|
||||
mHasConditions = Global.ConditionMgr.HasConditionsForNotGroupedEntry(ConditionSourceType.CreatureTemplateVehicle, creature.GetEntry());
|
||||
}
|
||||
|
||||
@@ -72,70 +66,43 @@ namespace Game.AI
|
||||
mDespawnTime -= diff;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (!HasEscortState(SmartEscortState.Escorting))//dont mess up escort movement after combat
|
||||
SetRun(mRun);
|
||||
GetScript().OnReset();
|
||||
}
|
||||
|
||||
WayPoint GetNextWayPoint()
|
||||
{
|
||||
if (mWayPoints.Empty())
|
||||
return null;
|
||||
|
||||
mCurrentWPID++;
|
||||
var wayPoint = mWayPoints.Find(p => p.Id == mCurrentWPID);
|
||||
if (wayPoint != null)
|
||||
{
|
||||
mLastWP = wayPoint;
|
||||
if (mLastWP.Id != mCurrentWPID)
|
||||
{
|
||||
Log.outError(LogFilter.Misc, "SmartAI.GetNextWayPoint: Got not expected waypoint id {mLastWP.id}, expected {mCurrentWPID}");
|
||||
}
|
||||
return wayPoint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void StartPath(bool run = false, uint path = 0, bool repeat = false, Unit invoker = null)
|
||||
public void StartPath(bool run = false, uint pathId = 0, bool repeat = false, Unit invoker = null, uint nodeId = 1)
|
||||
{
|
||||
if (me.IsInCombat())// no wp movement in combat
|
||||
{
|
||||
Log.outError(LogFilter.Server, "SmartAI.StartPath: Creature entry {0} wanted to start waypoint movement while in combat, ignoring.", me.GetEntry());
|
||||
Log.outError(LogFilter.Server, $"SmartAI.StartPath: Creature entry {me.GetEntry()} wanted to start waypoint movement while in combat, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(SmartEscortState.Escorting))
|
||||
StopPath();
|
||||
|
||||
if (path != 0)
|
||||
if (pathId != 0)
|
||||
{
|
||||
if (!LoadPath(path))
|
||||
if (!LoadPath(pathId))
|
||||
return;
|
||||
}
|
||||
|
||||
if (mWayPoints.Empty())
|
||||
if (_path.nodes.Empty())
|
||||
return;
|
||||
|
||||
WayPoint wp = GetNextWayPoint();
|
||||
if (wp != null)
|
||||
{
|
||||
AddEscortState(SmartEscortState.Escorting);
|
||||
mCanRepeatPath = repeat;
|
||||
_currentWaypointNode = nodeId;
|
||||
_waypointPathEnded = false;
|
||||
|
||||
SetRun(run);
|
||||
_repeatWaypointPath = repeat;
|
||||
|
||||
if (invoker != null && invoker.GetTypeId() == TypeId.Player)
|
||||
// Do not use AddEscortState, removing everything from previous
|
||||
_escortState = SmartEscortState.Escorting;
|
||||
|
||||
if (invoker && invoker.IsPlayer())
|
||||
{
|
||||
mEscortNPCFlags = me.m_unitData.NpcFlags[0];
|
||||
me.SetNpcFlags(NPCFlags.None);
|
||||
_escortNPCFlags = me.m_unitData.NpcFlags[0];
|
||||
me.SetNpcFlags((NPCFlags)0);
|
||||
}
|
||||
|
||||
mLastOOCPos = me.GetPosition();
|
||||
me.GetMotionMaster().MovePoint(wp.Id, wp.X, wp.Y, wp.Z);
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, wp.Id, GetScript().GetPathId());
|
||||
}
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, _currentWaypointNode, GetScript().GetPathId());
|
||||
|
||||
me.GetMotionMaster().MovePath(_path, _repeatWaypointPath);
|
||||
}
|
||||
|
||||
bool LoadPath(uint entry)
|
||||
@@ -143,13 +110,22 @@ namespace Game.AI
|
||||
if (HasEscortState(SmartEscortState.Escorting))
|
||||
return false;
|
||||
|
||||
mWayPoints = Global.SmartAIMgr.GetPath(entry);
|
||||
if (mWayPoints == null)
|
||||
WaypointPath path = Global.SmartAIMgr.GetPath(entry);
|
||||
if (path == null || path.nodes.Empty())
|
||||
{
|
||||
GetScript().SetPathId(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
_path.id = path.id;
|
||||
_path.nodes = path.nodes;
|
||||
foreach (WaypointNode waypoint in _path.nodes)
|
||||
{
|
||||
GridDefines.NormalizeMapCoord(ref waypoint.x);
|
||||
GridDefines.NormalizeMapCoord(ref waypoint.y);
|
||||
waypoint.moveType = mRun ? WaypointMoveType.Run : WaypointMoveType.Walk;
|
||||
}
|
||||
|
||||
GetScript().SetPathId(entry);
|
||||
return true;
|
||||
}
|
||||
@@ -165,20 +141,23 @@ namespace Game.AI
|
||||
return;
|
||||
}
|
||||
|
||||
mForcedPaused = forced;
|
||||
mLastOOCPos = me.GetPosition();
|
||||
AddEscortState(SmartEscortState.Paused);
|
||||
mWPPauseTimer = delay;
|
||||
_waypointPauseTimer = delay;
|
||||
|
||||
if (forced)
|
||||
{
|
||||
_waypointPauseForced = forced;
|
||||
SetRun(mRun);
|
||||
me.StopMoving();//force stop
|
||||
me.GetMotionMaster().MoveIdle();//force stop
|
||||
me.PauseMovement();
|
||||
me.SetHomePosition(me.GetPosition());
|
||||
}
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointPaused, null, mLastWP.Id, GetScript().GetPathId());
|
||||
else
|
||||
_waypointReached = false;
|
||||
|
||||
AddEscortState(SmartEscortState.Paused);
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointPaused, null, _currentWaypointNode, GetScript().GetPathId());
|
||||
}
|
||||
|
||||
public void StopPath(uint DespawnTime = 0, uint quest = 0, bool fail = false)
|
||||
public void StopPath(uint despawnTime = 0, uint quest = 0, bool fail = false)
|
||||
{
|
||||
if (!HasEscortState(SmartEscortState.Escorting))
|
||||
return;
|
||||
@@ -186,40 +165,28 @@ namespace Game.AI
|
||||
if (quest != 0)
|
||||
mEscortQuestID = quest;
|
||||
|
||||
SetDespawnTime(DespawnTime);
|
||||
//mDespawnTime = DespawnTime;
|
||||
if (mDespawnState != 2)
|
||||
SetDespawnTime(despawnTime);
|
||||
|
||||
mLastOOCPos = me.GetPosition();
|
||||
me.StopMoving();//force stop
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointStopped, null, mLastWP.Id, GetScript().GetPathId());
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointStopped, null, _currentWaypointNode, GetScript().GetPathId());
|
||||
|
||||
EndPath(fail);
|
||||
}
|
||||
|
||||
public void EndPath(bool fail = false)
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointEnded, null, mLastWP.Id, GetScript().GetPathId());
|
||||
|
||||
RemoveEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning);
|
||||
mWayPoints = null;
|
||||
mCurrentWPID = 0;
|
||||
mWPPauseTimer = 0;
|
||||
mLastWP = null;
|
||||
_path.nodes.Clear();
|
||||
_waypointPauseTimer = 0;
|
||||
|
||||
if (mEscortNPCFlags != 0)
|
||||
if (_escortNPCFlags != 0)
|
||||
{
|
||||
me.SetNpcFlags((NPCFlags)mEscortNPCFlags);
|
||||
mEscortNPCFlags = 0;
|
||||
me.SetNpcFlags((NPCFlags)_escortNPCFlags);
|
||||
_escortNPCFlags = 0;
|
||||
}
|
||||
|
||||
if (mCanRepeatPath)
|
||||
{
|
||||
if (IsAIControlled())
|
||||
StartPath(mRun, GetScript().GetPathId(), true);
|
||||
}
|
||||
else
|
||||
GetScript().SetPathId(0);
|
||||
|
||||
List<WorldObject> targets = GetScript().GetStoredTargetList(SharedConst.SmartEscortTargets, me);
|
||||
if (targets != null && mEscortQuestID != 0)
|
||||
{
|
||||
@@ -264,16 +231,36 @@ namespace Game.AI
|
||||
}
|
||||
}
|
||||
|
||||
// End Path events should be only processed if it was SUCCESSFUL stop or stop called by SMART_ACTION_WAYPOINT_STOP
|
||||
if (fail)
|
||||
return;
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointEnded, null, _currentWaypointNode, GetScript().GetPathId());
|
||||
|
||||
if (_repeatWaypointPath)
|
||||
{
|
||||
if (IsAIControlled())
|
||||
StartPath(mRun, GetScript().GetPathId(), _repeatWaypointPath);
|
||||
}
|
||||
else
|
||||
GetScript().SetPathId(0);
|
||||
|
||||
if (mDespawnState == 1)
|
||||
StartDespawn();
|
||||
}
|
||||
|
||||
public void ResumePath()
|
||||
{
|
||||
SetRun(mRun);
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointResumed, null, _currentWaypointNode, GetScript().GetPathId());
|
||||
|
||||
if (mLastWP != null)
|
||||
me.GetMotionMaster().MovePoint(mLastWP.Id, mLastWP.X, mLastWP.Y, mLastWP.Z);
|
||||
RemoveEscortState(SmartEscortState.Paused);
|
||||
|
||||
_waypointPauseForced = false;
|
||||
_waypointReached = false;
|
||||
_waypointPauseTimer = 0;
|
||||
|
||||
SetRun(mRun);
|
||||
me.ResumeMovement();
|
||||
}
|
||||
|
||||
void ReturnToLastOOCPos()
|
||||
@@ -281,8 +268,8 @@ namespace Game.AI
|
||||
if (!IsAIControlled())
|
||||
return;
|
||||
|
||||
SetRun(mRun);
|
||||
me.GetMotionMaster().MovePoint(EventId.SmartEscortLastOCCPoint, mLastOOCPos);
|
||||
me.SetWalk(false);
|
||||
me.GetMotionMaster().MovePoint(EventId.SmartEscortLastOCCPoint, me.GetHomePosition());
|
||||
}
|
||||
|
||||
void UpdatePath(uint diff)
|
||||
@@ -290,77 +277,48 @@ namespace Game.AI
|
||||
if (!HasEscortState(SmartEscortState.Escorting))
|
||||
return;
|
||||
|
||||
if (mEscortInvokerCheckTimer < diff)
|
||||
if (_escortInvokerCheckTimer < diff)
|
||||
{
|
||||
// Escort failed, no players in range
|
||||
if (!IsEscortInvokerInRange())
|
||||
{
|
||||
StopPath(0, mEscortQuestID, true);
|
||||
|
||||
// allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying
|
||||
GetScript().ProcessEventsFor(SmartEvents.Death, me);
|
||||
me.DespawnOrUnsummon(1);
|
||||
me.DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
mEscortInvokerCheckTimer = 1000;
|
||||
_escortInvokerCheckTimer = 1000;
|
||||
}
|
||||
else
|
||||
mEscortInvokerCheckTimer -= diff;
|
||||
_escortInvokerCheckTimer -= diff;
|
||||
|
||||
// handle pause
|
||||
if (HasEscortState(SmartEscortState.Paused))
|
||||
if (HasEscortState(SmartEscortState.Paused) && (_waypointReached || _waypointPauseForced))
|
||||
{
|
||||
if (mWPPauseTimer < diff)
|
||||
{
|
||||
if (!me.IsInCombat() && !HasEscortState(SmartEscortState.Returning) && (mWPReached || mLastWPIDReached == EventId.SmartEscortLastOCCPoint || mForcedPaused))
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointResumed, null, mLastWP.Id, GetScript().GetPathId());
|
||||
RemoveEscortState(SmartEscortState.Paused);
|
||||
if (mForcedPaused)// if paused between 2 wps resend movement
|
||||
if (_waypointPauseTimer < diff)
|
||||
{
|
||||
if (!me.IsInCombat() && !HasEscortState(SmartEscortState.Returning))
|
||||
ResumePath();
|
||||
mWPReached = false;
|
||||
mForcedPaused = false;
|
||||
}
|
||||
if (mLastWPIDReached == EventId.SmartEscortLastOCCPoint)
|
||||
mWPReached = true;
|
||||
}
|
||||
mWPPauseTimer = 0;
|
||||
}
|
||||
else
|
||||
mWPPauseTimer -= diff;
|
||||
_waypointPauseTimer -= diff;
|
||||
}
|
||||
else if (_waypointPathEnded) // end path
|
||||
{
|
||||
_waypointPathEnded = false;
|
||||
StopPath();
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(SmartEscortState.Returning))
|
||||
{
|
||||
if (mWPReached)//reached OOC WP
|
||||
if (_OOCReached)//reached OOC WP
|
||||
{
|
||||
_OOCReached = false;
|
||||
RemoveEscortState(SmartEscortState.Returning);
|
||||
if (!HasEscortState(SmartEscortState.Paused))
|
||||
ResumePath();
|
||||
mWPReached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!me.HasReactState(ReactStates.Passive) && me.IsInCombat()) || HasEscortState(SmartEscortState.Paused | SmartEscortState.Returning))
|
||||
return;
|
||||
|
||||
// handle next wp
|
||||
if (mWPReached)//reached WP
|
||||
{
|
||||
mWPReached = false;
|
||||
if (mCurrentWPID == GetWPCount())
|
||||
{
|
||||
EndPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
WayPoint wp = GetNextWayPoint();
|
||||
if (wp != null)
|
||||
{
|
||||
SetRun(mRun);
|
||||
me.GetMotionMaster().MovePoint(wp.Id, wp.X, wp.Y, wp.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +330,21 @@ namespace Game.AI
|
||||
UpdatePath(diff);
|
||||
UpdateDespawn(diff);
|
||||
|
||||
UpdateFollow(diff);
|
||||
if (!mFollowGuid.IsEmpty())
|
||||
{
|
||||
if (mFollowArrivedTimer < diff)
|
||||
{
|
||||
if (me.FindNearestCreature(mFollowArrivedEntry, SharedConst.InteractionDistance, true))
|
||||
{
|
||||
StopFollow(true);
|
||||
return;
|
||||
}
|
||||
|
||||
mFollowArrivedTimer = 1000;
|
||||
}
|
||||
else
|
||||
mFollowArrivedTimer -= diff;
|
||||
}
|
||||
|
||||
if (!IsAIControlled())
|
||||
return;
|
||||
@@ -427,25 +399,44 @@ namespace Game.AI
|
||||
return true;
|
||||
}
|
||||
|
||||
void MovepointReached(uint id)
|
||||
public override void WaypointStarted(uint nodeId, uint pathId)
|
||||
{
|
||||
if (id != EventId.SmartEscortLastOCCPoint && mLastWPIDReached != id)
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointReached, null, id);
|
||||
|
||||
mLastWPIDReached = id;
|
||||
mWPReached = true;
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType MovementType, uint Data)
|
||||
public override void WaypointReached(uint nodeId, uint pathId)
|
||||
{
|
||||
if ((MovementType == MovementGeneratorType.Point && Data == EventId.SmartEscortLastOCCPoint) || MovementType == MovementGeneratorType.Follow)
|
||||
_currentWaypointNode = nodeId;
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.WaypointReached, null, _currentWaypointNode, pathId);
|
||||
|
||||
if (_waypointPauseTimer != 0 && !_waypointPauseForced)
|
||||
{
|
||||
_waypointReached = true;
|
||||
me.PauseMovement();
|
||||
me.SetHomePosition(me.GetPosition());
|
||||
}
|
||||
else if (HasEscortState(SmartEscortState.Escorting) && me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint)
|
||||
{
|
||||
if (_currentWaypointNode == _path.nodes.Count)
|
||||
_waypointPathEnded = true;
|
||||
else
|
||||
SetRun(mRun);
|
||||
}
|
||||
}
|
||||
|
||||
public override void MovementInform(MovementGeneratorType movementType, uint id)
|
||||
{
|
||||
if (movementType == MovementGeneratorType.Point && id == EventId.SmartEscortLastOCCPoint)
|
||||
me.ClearUnitState(UnitState.Evade);
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.Movementinform, null, (uint)MovementType, Data);
|
||||
if (MovementType != MovementGeneratorType.Point || !HasEscortState(SmartEscortState.Escorting))
|
||||
GetScript().ProcessEventsFor(SmartEvents.Movementinform, null, (uint)movementType, id);
|
||||
|
||||
if (!HasEscortState(SmartEscortState.Escorting))
|
||||
return;
|
||||
|
||||
MovepointReached(Data);
|
||||
if (movementType != MovementGeneratorType.Point && id == EventId.SmartEscortLastOCCPoint)
|
||||
_OOCReached = true;
|
||||
}
|
||||
|
||||
public override void EnterEvadeMode(EvadeReason why = EvadeReason.Other)
|
||||
@@ -470,7 +461,6 @@ namespace Game.AI
|
||||
GetScript().ProcessEventsFor(SmartEvents.Evade);//must be after aura clear so we can cast spells from db
|
||||
|
||||
SetRun(mRun);
|
||||
|
||||
if (HasEscortState(SmartEscortState.Escorting))
|
||||
{
|
||||
AddEscortState(SmartEscortState.Returning);
|
||||
@@ -496,8 +486,8 @@ namespace Game.AI
|
||||
me.GetMotionMaster().MoveTargetedHome();
|
||||
}
|
||||
|
||||
if (!HasEscortState(SmartEscortState.Escorting)) //dont mess up escort movement after combat
|
||||
SetRun(mRun);
|
||||
if (!me.HasUnitState(UnitState.Evade))
|
||||
GetScript().OnReset();
|
||||
}
|
||||
|
||||
public override void MoveInLineOfSight(Unit who)
|
||||
@@ -510,7 +500,7 @@ namespace Game.AI
|
||||
if (!IsAIControlled())
|
||||
return;
|
||||
|
||||
if (AssistPlayerInCombatAgainst(who))
|
||||
if (HasEscortState(SmartEscortState.Escorting) && AssistPlayerInCombatAgainst(who))
|
||||
return;
|
||||
|
||||
base.MoveInLineOfSight(who);
|
||||
@@ -537,11 +527,24 @@ namespace Game.AI
|
||||
if (who.GetVictim().GetCharmerOrOwnerPlayerOrPlayerItself() == null)
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (!who.IsInAccessiblePlaceFor(me))
|
||||
return false;
|
||||
|
||||
if (!CanAIAttack(who))
|
||||
return false;
|
||||
|
||||
// we cannot attack in evade mode
|
||||
if (me.IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
// or if enemy is in evade mode
|
||||
if (who.IsCreature() && who.ToCreature().IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
if (!me.IsValidAssistTarget(who.GetVictim()))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
//too far away and no free sight
|
||||
if (me.IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me.IsWithinLOSInMap(who))
|
||||
{
|
||||
me.EngageWithTarget(who);
|
||||
@@ -556,7 +559,7 @@ namespace Game.AI
|
||||
mDespawnTime = 0;
|
||||
mRespawnTime = 0;
|
||||
mDespawnState = 0;
|
||||
mEscortState = SmartEscortState.None;
|
||||
_escortState = SmartEscortState.None;
|
||||
me.SetVisible(true);
|
||||
if (me.GetFaction() != me.GetCreatureTemplate().Faction)
|
||||
me.RestoreFaction();
|
||||
@@ -579,9 +582,18 @@ namespace Game.AI
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.ReachedHome);
|
||||
|
||||
if (!UpdateVictim() && me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle && me.GetWaypointPath() != 0)
|
||||
CreatureGroup formation = me.GetFormation();
|
||||
if (formation == null || formation.GetLeader() == me || !formation.IsFormed())
|
||||
{
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle && me.GetWaypointPath() != 0)
|
||||
me.GetMotionMaster().MovePath(me.GetWaypointPath(), true);
|
||||
else
|
||||
me.ResumeMovement();
|
||||
}
|
||||
else if (formation.IsFormed())
|
||||
me.GetMotionMaster().MoveIdle(); // wait the order of leader
|
||||
}
|
||||
|
||||
mJustReset = false;
|
||||
}
|
||||
|
||||
@@ -591,25 +603,14 @@ namespace Game.AI
|
||||
me.InterruptNonMeleeSpells(false); // must be before ProcessEvents
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.Aggro, victim);
|
||||
|
||||
if (!IsAIControlled())
|
||||
return;
|
||||
|
||||
mLastOOCPos = me.GetPosition();
|
||||
SetRun(mRun);
|
||||
if (me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Point)
|
||||
me.GetMotionMaster().MovementExpired();
|
||||
}
|
||||
|
||||
public override void JustDied(Unit killer)
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.Death, killer);
|
||||
if (HasEscortState(SmartEscortState.Escorting))
|
||||
{
|
||||
EndPath(true);
|
||||
me.StopMoving();//force stop
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
|
||||
GetScript().ProcessEventsFor(SmartEvents.Death, killer);
|
||||
}
|
||||
|
||||
public override void KilledUnit(Unit victim)
|
||||
@@ -627,26 +628,32 @@ namespace Game.AI
|
||||
// dont allow charmed npcs to act on their own
|
||||
if (!IsAIControlled())
|
||||
{
|
||||
if (who != null && mCanAutoAttack)
|
||||
me.Attack(who, true);
|
||||
if (who != null)
|
||||
me.Attack(who, mCanAutoAttack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (who != null && me.Attack(who, me.IsWithinMeleeRange(who)))
|
||||
if (who != null && me.Attack(who, mCanAutoAttack))
|
||||
{
|
||||
me.GetMotionMaster().Clear(MovementSlot.Active);
|
||||
me.PauseMovement();
|
||||
|
||||
if (mCanCombatMove)
|
||||
{
|
||||
SetRun(mRun);
|
||||
me.GetMotionMaster().MoveChase(who);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SpellHit(Unit caster, SpellInfo spell)
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spell);
|
||||
}
|
||||
|
||||
public override void SpellHitTarget(Unit target, SpellInfo spell)
|
||||
public override void SpellHit(Unit caster, SpellInfo spellInfo)
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.SpellHitTarget, target, 0, 0, false, spell);
|
||||
GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spellInfo);
|
||||
}
|
||||
|
||||
public override void SpellHitTarget(Unit target, SpellInfo spellInfo)
|
||||
{
|
||||
GetScript().ProcessEventsFor(SmartEvents.SpellHitTarget, target, 0, 0, false, spellInfo);
|
||||
}
|
||||
|
||||
public override void DamageTaken(Unit attacker, ref uint damage)
|
||||
@@ -698,10 +705,10 @@ namespace Game.AI
|
||||
public override void InitializeAI()
|
||||
{
|
||||
mScript.OnInitialize(me);
|
||||
|
||||
if (!me.IsDead())
|
||||
{
|
||||
mJustReset = true;
|
||||
JustReachedHome();
|
||||
GetScript().OnReset();
|
||||
GetScript().ProcessEventsFor(SmartEvents.Respawn);
|
||||
}
|
||||
}
|
||||
@@ -712,13 +719,13 @@ namespace Game.AI
|
||||
{
|
||||
if (HasEscortState(SmartEscortState.Escorting | SmartEscortState.Paused | SmartEscortState.Returning))
|
||||
EndPath(true);
|
||||
me.StopMoving();
|
||||
}
|
||||
|
||||
mIsCharmed = apply;
|
||||
|
||||
if (!apply && !me.IsInEvadeMode())
|
||||
{
|
||||
if (mCanRepeatPath)
|
||||
if (_repeatWaypointPath)
|
||||
StartPath(mRun, GetScript().GetPathId(), true);
|
||||
else
|
||||
me.SetWalk(!mRun);
|
||||
@@ -808,41 +815,25 @@ namespace Game.AI
|
||||
GetScript().ProcessEventsFor(SmartEvents.RewardQuest, player, quest.Id, opt);
|
||||
}
|
||||
|
||||
public override void OnGameEvent(bool start, ushort eventId)
|
||||
{
|
||||
GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId);
|
||||
}
|
||||
|
||||
public void SetCombatMove(bool on)
|
||||
{
|
||||
if (mCanCombatMove == on)
|
||||
return;
|
||||
|
||||
mCanCombatMove = on;
|
||||
|
||||
if (!IsAIControlled())
|
||||
return;
|
||||
|
||||
if (!HasEscortState(SmartEscortState.Escorting))
|
||||
if (me.IsEngaged())
|
||||
{
|
||||
if (on && me.GetVictim() != null)
|
||||
{
|
||||
if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle)
|
||||
if (on && !me.HasReactState(ReactStates.Passive) && me.GetVictim() && me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Max)
|
||||
{
|
||||
SetRun(mRun);
|
||||
me.GetMotionMaster().MoveChase(me.GetVictim());
|
||||
me.CastStop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me.HasUnitState(UnitState.ConfusedMove | UnitState.FleeingMove))
|
||||
return;
|
||||
|
||||
me.GetMotionMaster().MovementExpired();
|
||||
me.GetMotionMaster().Clear(true);
|
||||
me.StopMoving();
|
||||
me.GetMotionMaster().MoveIdle();
|
||||
}
|
||||
else if (!on && me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Chase)
|
||||
me.GetMotionMaster().Clear(MovementSlot.Active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,6 +892,11 @@ namespace Game.AI
|
||||
GetScript().SetScript9(e, entry);
|
||||
}
|
||||
|
||||
public override void OnGameEvent(bool start, ushort eventId)
|
||||
{
|
||||
GetScript().ProcessEventsFor(start ? SmartEvents.GameEventStart : SmartEvents.GameEventEnd, null, eventId);
|
||||
}
|
||||
|
||||
public override void OnSpellClick(Unit clicker, ref bool result)
|
||||
{
|
||||
if (!result)
|
||||
@@ -943,29 +939,18 @@ namespace Game.AI
|
||||
mConditionsTimer -= diff;
|
||||
}
|
||||
|
||||
public void UpdateFollow(uint diff)
|
||||
public override void Reset()
|
||||
{
|
||||
if (!mFollowGuid.IsEmpty())
|
||||
{
|
||||
if (mFollowArrivedTimer < diff)
|
||||
{
|
||||
if (me.FindNearestCreature(mFollowArrivedEntry, SharedConst.InteractionDistance, true) != null)
|
||||
{
|
||||
StopFollow(true);
|
||||
return;
|
||||
if (!HasEscortState(SmartEscortState.Escorting))//dont mess up escort movement after combat
|
||||
SetRun(mRun);
|
||||
GetScript().OnReset();
|
||||
}
|
||||
|
||||
mFollowArrivedTimer = 1000;
|
||||
}
|
||||
else
|
||||
mFollowArrivedTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasEscortState(SmartEscortState uiEscortState) { return mEscortState.HasAnyFlag(uiEscortState); }
|
||||
void AddEscortState(SmartEscortState uiEscortState) { mEscortState |= uiEscortState; }
|
||||
void RemoveEscortState(SmartEscortState uiEscortState) { mEscortState &= ~uiEscortState; }
|
||||
public bool HasEscortState(SmartEscortState uiEscortState) { return (_escortState & uiEscortState) != 0; }
|
||||
public void AddEscortState(SmartEscortState uiEscortState) { _escortState |= uiEscortState; }
|
||||
public void RemoveEscortState(SmartEscortState uiEscortState) { _escortState &= ~uiEscortState; }
|
||||
public void SetAutoAttack(bool on) { mCanAutoAttack = on; }
|
||||
|
||||
public bool CanCombatMove() { return mCanCombatMove; }
|
||||
|
||||
public SmartScript GetScript() { return mScript; }
|
||||
@@ -976,17 +961,19 @@ namespace Game.AI
|
||||
{
|
||||
mDespawnTime = t;
|
||||
mRespawnTime = r;
|
||||
mDespawnState = (uint)(t != 0 ? 1 : 0);
|
||||
mDespawnState = t != 0 ? 1 : 0u;
|
||||
}
|
||||
|
||||
public void StartDespawn() { mDespawnState = 2; }
|
||||
|
||||
uint GetWPCount() { return (uint)mWayPoints?.Count; }
|
||||
|
||||
public void SetWPPauseTimer(uint time) { mWPPauseTimer = time; }
|
||||
public void SetWPPauseTimer(uint time) { _waypointPauseTimer = time; }
|
||||
|
||||
public void SetGossipReturn(bool val) { _gossipReturn = val; }
|
||||
|
||||
public uint mEscortQuestID;
|
||||
|
||||
SmartScript mScript = new SmartScript();
|
||||
|
||||
bool mIsCharmed;
|
||||
uint mFollowCreditType;
|
||||
uint mFollowArrivedTimer;
|
||||
@@ -996,31 +983,27 @@ namespace Game.AI
|
||||
float mFollowDist;
|
||||
float mFollowAngle;
|
||||
|
||||
SmartScript mScript = new SmartScript();
|
||||
List<WayPoint> mWayPoints;
|
||||
SmartEscortState mEscortState;
|
||||
uint mCurrentWPID;
|
||||
uint mLastWPIDReached;
|
||||
bool mWPReached;
|
||||
uint mWPPauseTimer;
|
||||
uint mEscortNPCFlags;
|
||||
WayPoint mLastWP;
|
||||
Position mLastOOCPos;//set on enter combat
|
||||
bool mCanRepeatPath;
|
||||
SmartEscortState _escortState;
|
||||
uint _escortNPCFlags;
|
||||
uint _escortInvokerCheckTimer;
|
||||
WaypointPath _path;
|
||||
uint _currentWaypointNode;
|
||||
bool _waypointReached;
|
||||
uint _waypointPauseTimer;
|
||||
bool _waypointPauseForced;
|
||||
bool _repeatWaypointPath;
|
||||
bool _OOCReached;
|
||||
bool _waypointPathEnded;
|
||||
|
||||
bool mRun;
|
||||
bool mEvadeDisabled;
|
||||
bool mCanAutoAttack;
|
||||
bool mCanCombatMove;
|
||||
bool mForcedPaused;
|
||||
uint mInvincibilityHpLevel;
|
||||
|
||||
uint mDespawnTime;
|
||||
uint mRespawnTime;
|
||||
uint mDespawnState;
|
||||
|
||||
public uint mEscortQuestID;
|
||||
|
||||
uint mEscortInvokerCheckTimer;
|
||||
bool mJustReset;
|
||||
|
||||
// Vehicle conditions
|
||||
|
||||
@@ -22,6 +22,7 @@ using Game.Entities;
|
||||
using Game.Spells;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Game.AI
|
||||
@@ -296,11 +297,12 @@ namespace Game.AI
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SmartAI scripts in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public void LoadWaypointFromDB()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
waypoint_map.Clear();
|
||||
_waypointStore.Clear();
|
||||
|
||||
PreparedStatement stmt = DB.World.GetPreparedStatement(WorldStatements.SEL_SMARTAI_WP);
|
||||
SQLResult result = DB.World.Query(stmt);
|
||||
@@ -314,8 +316,8 @@ namespace Game.AI
|
||||
|
||||
uint count = 0;
|
||||
uint total = 0;
|
||||
uint last_entry = 0;
|
||||
uint last_id = 1;
|
||||
uint lastEntry = 0;
|
||||
uint lastId = 1;
|
||||
|
||||
do
|
||||
{
|
||||
@@ -325,25 +327,30 @@ namespace Game.AI
|
||||
float y = result.Read<float>(3);
|
||||
float z = result.Read<float>(4);
|
||||
|
||||
if (last_entry != entry)
|
||||
if (lastEntry != entry)
|
||||
{
|
||||
last_id = 1;
|
||||
count++;
|
||||
lastId = 1;
|
||||
++count;
|
||||
}
|
||||
|
||||
if (last_id != id)
|
||||
Log.outError(LogFilter.Sql, "SmartWaypointMgr.LoadFromDB: Path entry {0}, unexpected point id {1}, expected {2}.", entry, id, last_id);
|
||||
if (lastId != id)
|
||||
Log.outError(LogFilter.Sql, $"SmartWaypointMgr.LoadFromDB: Path entry {entry}, unexpected point id {id}, expected {lastId}.");
|
||||
|
||||
last_id++;
|
||||
++lastId;
|
||||
|
||||
waypoint_map.Add(entry, new WayPoint(id, x, y, z));
|
||||
if (!_waypointStore.ContainsKey(entry))
|
||||
_waypointStore[entry] = new WaypointPath();
|
||||
|
||||
last_entry = entry;
|
||||
total++;
|
||||
WaypointPath path = _waypointStore[entry];
|
||||
path.id = entry;
|
||||
path.nodes.Add(new WaypointNode(id, x, y, z));
|
||||
|
||||
lastEntry = entry;
|
||||
++total;
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SmartAI waypoint paths (total {1} waypoints) in {2} ms", count, total, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} SmartAI waypoint paths (total {total} waypoints) in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
|
||||
}
|
||||
|
||||
bool IsTargetValid(SmartScriptHolder e)
|
||||
@@ -1110,17 +1117,19 @@ namespace Game.AI
|
||||
break;
|
||||
case SmartActions.WpStart:
|
||||
{
|
||||
List<WayPoint> path = Global.SmartAIMgr.GetPath(e.Action.wpStart.pathID);
|
||||
if (path.Empty())
|
||||
WaypointPath path = GetPath(e.Action.wpStart.pathID);
|
||||
if (path == null || path.nodes.Empty())
|
||||
{
|
||||
Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses non-existent WaypointPath id {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.wpStart.pathID);
|
||||
Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses non-existent WaypointPath id {e.Action.wpStart.pathID}, skipped.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.Action.wpStart.quest != 0 && !IsQuestValid(e, e.Action.wpStart.quest))
|
||||
return false;
|
||||
|
||||
if (e.Action.wpStart.reactState > (uint)ReactStates.Aggressive)
|
||||
{
|
||||
Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Creature {0} Event {1} Action {2} uses invalid React State {3}, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.Action.wpStart.reactState);
|
||||
Log.outError(LogFilter.ScriptsAi, $"SmartAIMgr: {e} uses invalid React State {e.Action.wpStart.reactState}, skipped.");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -1558,9 +1567,9 @@ namespace Game.AI
|
||||
return temp;
|
||||
}
|
||||
|
||||
public List<WayPoint> GetPath(uint id)
|
||||
public WaypointPath GetPath(uint id)
|
||||
{
|
||||
return waypoint_map.LookupByKey(id);
|
||||
return _waypointStore.LookupByKey(id);
|
||||
}
|
||||
|
||||
public SmartScriptHolder FindLinkedSourceEvent(List<SmartScriptHolder> list, uint eventId)
|
||||
@@ -1582,7 +1591,7 @@ namespace Game.AI
|
||||
}
|
||||
|
||||
MultiMap<int, SmartScriptHolder>[] mEventMap = new MultiMap<int, SmartScriptHolder>[(int)SmartScriptType.Max];
|
||||
MultiMap<uint, WayPoint> waypoint_map = new MultiMap<uint, WayPoint>();
|
||||
Dictionary<uint, WaypointPath> _waypointStore = new Dictionary<uint, WaypointPath>();
|
||||
|
||||
Dictionary<SmartScriptType, uint> SmartAITypeMask = new Dictionary<SmartScriptType, uint>
|
||||
{
|
||||
@@ -1688,22 +1697,6 @@ namespace Game.AI
|
||||
};
|
||||
}
|
||||
|
||||
public class WayPoint
|
||||
{
|
||||
public WayPoint(uint id, float x, float y, float z)
|
||||
{
|
||||
Id = id;
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
public uint Id;
|
||||
public float X;
|
||||
public float Y;
|
||||
public float Z;
|
||||
}
|
||||
|
||||
public class SmartScriptHolder
|
||||
{
|
||||
public SmartScriptHolder() { }
|
||||
|
||||
@@ -1988,7 +1988,8 @@ namespace Game.AI
|
||||
waypoints.Add(id);
|
||||
|
||||
float distanceToClosest = float.MaxValue;
|
||||
WayPoint closestWp = null;
|
||||
uint closestPathId = 0;
|
||||
uint closestWaypointId = 0;
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
@@ -1997,26 +1998,26 @@ namespace Game.AI
|
||||
{
|
||||
if (IsSmart(creature))
|
||||
{
|
||||
foreach (uint wpId in waypoints)
|
||||
foreach (uint pathId in waypoints)
|
||||
{
|
||||
var path = Global.SmartAIMgr.GetPath(wpId);
|
||||
if (path == null || path.Empty())
|
||||
WaypointPath path = Global.SmartAIMgr.GetPath(pathId);
|
||||
if (path == null || path.nodes.Empty())
|
||||
continue;
|
||||
|
||||
WayPoint wp = path[0];
|
||||
if (wp != null)
|
||||
foreach (var waypoint in path.nodes)
|
||||
{
|
||||
float distToThisPath = creature.GetDistance(wp.X, wp.Y, wp.Z);
|
||||
float distToThisPath = creature.GetDistance(waypoint.x, waypoint.y, waypoint.z);
|
||||
if (distToThisPath < distanceToClosest)
|
||||
{
|
||||
distanceToClosest = distToThisPath;
|
||||
closestWp = wp;
|
||||
closestPathId = pathId;
|
||||
closestWaypointId = waypoint.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closestWp != null)
|
||||
((SmartAI)creature.GetAI()).StartPath(false, closestWp.Id, true);
|
||||
if (closestPathId != 0)
|
||||
((SmartAI)creature.GetAI()).StartPath(false, closestPathId, true, null, closestWaypointId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,21 @@ namespace Game.Entities
|
||||
ForcedDespawn(0);
|
||||
}
|
||||
|
||||
public override void PauseMovement(uint timer = 0, MovementSlot slot = 0)
|
||||
{
|
||||
base.PauseMovement(timer, slot);
|
||||
|
||||
SetHomePosition(GetPosition());
|
||||
}
|
||||
|
||||
public bool IsReturningHome()
|
||||
{
|
||||
if (GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Home)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SearchFormation()
|
||||
{
|
||||
if (IsSummon())
|
||||
@@ -119,6 +134,33 @@ namespace Game.Entities
|
||||
FormationMgr.AddCreatureToGroup(frmdata.leaderGUID, this);
|
||||
}
|
||||
|
||||
public bool IsFormationLeader()
|
||||
{
|
||||
if (m_formation == null)
|
||||
return false;
|
||||
|
||||
return m_formation.IsLeader(this);
|
||||
}
|
||||
|
||||
public void SignalFormationMovement(Position destination, uint id = 0, WaypointMoveType moveType = 0, bool orientation = false)
|
||||
{
|
||||
if (m_formation == null)
|
||||
return;
|
||||
|
||||
if (!m_formation.IsLeader(this))
|
||||
return;
|
||||
|
||||
m_formation.LeaderMoveTo(destination, id, moveType, orientation);
|
||||
}
|
||||
|
||||
public bool IsFormationLeaderMoveAllowed()
|
||||
{
|
||||
if (m_formation == null)
|
||||
return false;
|
||||
|
||||
return m_formation.CanLeaderStartMoving();
|
||||
}
|
||||
|
||||
public void RemoveCorpse(bool setSpawnTime = true, bool destroyForNearbyPlayers = true)
|
||||
{
|
||||
if (GetDeathState() != DeathState.Corpse)
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Game.Entities
|
||||
m_Formed = !dismiss;
|
||||
}
|
||||
|
||||
public void LeaderMoveTo(Position destination, uint id = 0, uint moveType = 0, bool orientation = false)
|
||||
public void LeaderMoveTo(Position destination, uint id = 0, WaypointMoveType moveType = 0, bool orientation = false)
|
||||
{
|
||||
//! To do: This should probably get its own movement generator or use WaypointMovementGenerator.
|
||||
//! If the leader's path is known, member's path can be plotted as well using formation offsets.
|
||||
@@ -258,10 +258,25 @@ namespace Game.Entities
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanLeaderStartMoving()
|
||||
{
|
||||
foreach (var itr in m_members)
|
||||
{
|
||||
if (itr.Key != m_leader && itr.Key.IsAlive())
|
||||
{
|
||||
if (itr.Key.IsEngaged() || itr.Key.IsReturningHome())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Creature GetLeader() { return m_leader; }
|
||||
public uint GetId() { return m_groupID; }
|
||||
public bool IsEmpty() { return m_members.Empty(); }
|
||||
public bool IsFormed() { return m_Formed; }
|
||||
public bool IsLeader(Creature creature) { return m_leader == creature; }
|
||||
|
||||
Creature m_leader;
|
||||
Dictionary<Creature, FormationInfo> m_members = new Dictionary<Creature, FormationInfo>();
|
||||
|
||||
@@ -159,6 +159,28 @@ namespace Game.Entities
|
||||
init.Stop();
|
||||
}
|
||||
|
||||
public virtual void PauseMovement(uint timer = 0, MovementSlot slot = 0)
|
||||
{
|
||||
if (slot >= MovementSlot.Max)
|
||||
return;
|
||||
|
||||
IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(slot);
|
||||
if (movementGenerator != null)
|
||||
movementGenerator.Pause(timer);
|
||||
|
||||
StopMoving();
|
||||
}
|
||||
|
||||
public void ResumeMovement(uint timer = 0, MovementSlot slot = 0)
|
||||
{
|
||||
if (slot >= MovementSlot.Max)
|
||||
return;
|
||||
|
||||
IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(slot);
|
||||
if (movementGenerator != null)
|
||||
movementGenerator.Resume(timer);
|
||||
}
|
||||
|
||||
public void SetInFront(WorldObject target)
|
||||
{
|
||||
if (!HasUnitState(UnitState.CannotTurn))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.AI;
|
||||
using Game.Movement;
|
||||
using Game.Networking.Packets;
|
||||
using Game.Spells;
|
||||
using System.Collections.Generic;
|
||||
@@ -351,8 +352,15 @@ namespace Game.Entities
|
||||
|
||||
if (IsTypeId(TypeId.Unit))
|
||||
{
|
||||
IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(MovementSlot.Idle);
|
||||
if (movementGenerator != null)
|
||||
movementGenerator.Pause(0);
|
||||
|
||||
GetMotionMaster().Clear(MovementSlot.Active);
|
||||
|
||||
StopMoving();
|
||||
|
||||
ToCreature().GetAI().OnCharmed(true);
|
||||
GetMotionMaster().MoveIdle();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -466,6 +474,7 @@ namespace Game.Entities
|
||||
else
|
||||
RestoreFaction();
|
||||
|
||||
///@todo Handle SLOT_IDLE motion resume
|
||||
GetMotionMaster().InitDefault();
|
||||
|
||||
Creature creature = ToCreature();
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Game
|
||||
return;
|
||||
|
||||
// Stop the npc if moving
|
||||
unit.StopMoving();
|
||||
unit.PauseMovement(WorldConfig.GetUIntValue(WorldCfg.CreatureStopForPlayer));
|
||||
|
||||
BattlegroundTypeId bgTypeId = Global.BattlegroundMgr.GetBattleMasterBG(unit.GetEntry());
|
||||
|
||||
|
||||
@@ -340,8 +340,8 @@ namespace Game
|
||||
if (!seamlessTeleport)
|
||||
{
|
||||
// short preparations to continue flight
|
||||
FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().Top();
|
||||
flight.Initialize(GetPlayer());
|
||||
IMovementGenerator movementGenerator = GetPlayer().GetMotionMaster().Top();
|
||||
movementGenerator.Initialize(GetPlayer());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,9 +143,8 @@ namespace Game
|
||||
|
||||
GetPlayer().RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Talk);
|
||||
|
||||
// and if he has pure gossip or is banker and moves or is tabard designer?
|
||||
//if (unit->IsArmorer() || unit->IsCivilian() || unit->IsQuestGiver() || unit->IsServiceProvider() || unit->IsGuard())
|
||||
unit.StopMoving();
|
||||
// Stop the npc if moving
|
||||
unit.PauseMovement(WorldConfig.GetUIntValue(WorldCfg.CreatureStopForPlayer));
|
||||
|
||||
// If spiritguide, no need for gossip menu, just put player into resurrect queue
|
||||
if (unit.IsSpiritGuide())
|
||||
@@ -464,8 +463,7 @@ namespace Game
|
||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
// Stop the npc if moving
|
||||
if (vendor.HasUnitState(UnitState.Moving))
|
||||
vendor.StopMoving();
|
||||
vendor.PauseMovement(WorldConfig.GetUIntValue(WorldCfg.CreatureStopForPlayer));
|
||||
|
||||
VendorItemData vendorItems = vendor.GetVendorItems();
|
||||
int rawItemCount = vendorItems != null ? vendorItems.GetItemCount() : 0;
|
||||
|
||||
@@ -71,8 +71,9 @@ namespace Game
|
||||
// remove fake death
|
||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
// Stop the npc if moving
|
||||
creature.StopMoving();
|
||||
creature.PauseMovement(WorldConfig.GetUIntValue(WorldCfg.CreatureStopForPlayer));
|
||||
|
||||
_player.PlayerTalkClass.ClearMenus();
|
||||
if (creature.GetAI().GossipHello(_player))
|
||||
|
||||
@@ -130,8 +130,7 @@ namespace Game
|
||||
if (GetPlayer().HasUnitState(UnitState.Died))
|
||||
GetPlayer().RemoveAurasByType(AuraType.FeignDeath);
|
||||
|
||||
while (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight)
|
||||
GetPlayer().GetMotionMaster().MovementExpired(false);
|
||||
GetPlayer().GetMotionMaster().Clear(MovementSlot.Controlled);
|
||||
|
||||
if (mountDisplayId != 0)
|
||||
GetPlayer().Mount(mountDisplayId);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Game.Movement
|
||||
{
|
||||
public class FormationMovementGenerator : MovementGeneratorMedium<Creature>
|
||||
{
|
||||
public FormationMovementGenerator(uint id, Position destination, uint moveType, bool run, bool orientation)
|
||||
public FormationMovementGenerator(uint id, Position destination, WaypointMoveType moveType, bool run, bool orientation)
|
||||
{
|
||||
_movementId = id;
|
||||
_destination = destination;
|
||||
@@ -51,16 +51,16 @@ namespace Game.Movement
|
||||
|
||||
switch (_moveType)
|
||||
{
|
||||
case 2: // WAYPOINT_MOVE_TYPE_LAND
|
||||
case WaypointMoveType.Land:
|
||||
init.SetAnimation(AnimType.ToGround);
|
||||
break;
|
||||
case 3: // WAYPOINT_MOVE_TYPE_TAKEOFF
|
||||
case WaypointMoveType.Takeoff:
|
||||
init.SetAnimation(AnimType.ToFly);
|
||||
break;
|
||||
case 1: // WAYPOINT_MOVE_TYPE_RUN
|
||||
case WaypointMoveType.Run:
|
||||
init.SetWalk(false);
|
||||
break;
|
||||
case 0: // WAYPOINT_MOVE_TYPE_WALK
|
||||
case WaypointMoveType.Walk:
|
||||
init.SetWalk(true);
|
||||
break;
|
||||
}
|
||||
@@ -103,16 +103,16 @@ namespace Game.Movement
|
||||
|
||||
switch (_moveType)
|
||||
{
|
||||
case 2: // WAYPOINT_MOVE_TYPE_LAND
|
||||
case WaypointMoveType.Land:
|
||||
init.SetAnimation(AnimType.ToGround);
|
||||
break;
|
||||
case 3: // WAYPOINT_MOVE_TYPE_TAKEOFF
|
||||
case WaypointMoveType.Takeoff:
|
||||
init.SetAnimation(AnimType.ToFly);
|
||||
break;
|
||||
case 1: // WAYPOINT_MOVE_TYPE_RUN
|
||||
case WaypointMoveType.Run:
|
||||
init.SetWalk(false);
|
||||
break;
|
||||
case 0: // WAYPOINT_MOVE_TYPE_WALK
|
||||
case WaypointMoveType.Walk:
|
||||
init.SetWalk(true);
|
||||
break;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace Game.Movement
|
||||
|
||||
uint _movementId;
|
||||
Position _destination;
|
||||
uint _moveType;
|
||||
WaypointMoveType _moveType;
|
||||
bool _run;
|
||||
bool _orientation;
|
||||
bool _recalculateSpeed;
|
||||
|
||||
@@ -35,6 +35,10 @@ namespace Game.Movement
|
||||
|
||||
public virtual void UnitSpeedChanged() { }
|
||||
|
||||
public virtual void Pause(uint timer = 0) { }
|
||||
|
||||
public virtual void Resume(uint overrideTimer = 0) { }
|
||||
|
||||
// used by Evade code for select point to evade with expected restart default movement
|
||||
public virtual bool GetResetPosition(Unit u, out float x, out float y, out float z)
|
||||
{
|
||||
|
||||
@@ -68,8 +68,7 @@ namespace Game.Movement
|
||||
// Call for creature group update
|
||||
Creature creature = owner.ToCreature();
|
||||
if (creature != null)
|
||||
if (creature.GetFormation() != null && creature.GetFormation().GetLeader() == creature)
|
||||
creature.GetFormation().LeaderMoveTo(_destination, _movementId);
|
||||
creature.SignalFormationMovement(_destination, _movementId);
|
||||
}
|
||||
|
||||
public override void DoReset(T owner)
|
||||
@@ -109,8 +108,7 @@ namespace Game.Movement
|
||||
// Call for creature group update
|
||||
Creature creature = owner.ToCreature();
|
||||
if (creature != null)
|
||||
if (creature.GetFormation() != null && creature.GetFormation().GetLeader() == creature)
|
||||
creature.GetFormation().LeaderMoveTo(_destination, _movementId);
|
||||
creature.SignalFormationMovement(_destination, _movementId);
|
||||
}
|
||||
|
||||
return !owner.MoveSpline.Finalized();
|
||||
|
||||
@@ -117,8 +117,7 @@ namespace Game.Movement
|
||||
_timer.Reset(traveltime + resetTimer);
|
||||
|
||||
// Call for creature group update
|
||||
if (owner.GetFormation() != null && owner.GetFormation().GetLeader() == owner)
|
||||
owner.GetFormation().LeaderMoveTo(position);
|
||||
owner.SignalFormationMovement(position);
|
||||
}
|
||||
|
||||
public override MovementGeneratorType GetMovementGeneratorType()
|
||||
|
||||
@@ -28,21 +28,45 @@ namespace Game.Movement
|
||||
{
|
||||
public class WaypointMovementGenerator : MovementGeneratorMedium<Creature>
|
||||
{
|
||||
const int FLIGHT_TRAVEL_UPDATE = 100;
|
||||
const int TIMEDIFF_NEXT_WP = 250;
|
||||
|
||||
public WaypointMovementGenerator(uint pathid = 0, bool _repeating = true)
|
||||
public WaypointMovementGenerator(uint pathId = 0, bool repeating = true)
|
||||
{
|
||||
nextMoveTime = new TimeTrackerSmall(0);
|
||||
isArrivalDone = false;
|
||||
pathId = pathid;
|
||||
repeating = _repeating;
|
||||
_nextMoveTime = new TimeTrackerSmall(0);
|
||||
_pathId = pathId;
|
||||
_repeating = repeating;
|
||||
_loadedFromDB = true;
|
||||
}
|
||||
|
||||
public override void DoReset(Creature creature)
|
||||
public WaypointMovementGenerator(WaypointPath path, bool repeating = true)
|
||||
{
|
||||
creature.AddUnitState(UnitState.Roaming | UnitState.RoamingMove);
|
||||
StartMoveNow(creature);
|
||||
_nextMoveTime = new TimeTrackerSmall(0);
|
||||
_repeating = repeating;
|
||||
_path = path;
|
||||
}
|
||||
|
||||
void LoadPath(Creature creature)
|
||||
{
|
||||
if (_loadedFromDB)
|
||||
{
|
||||
if (_pathId == 0)
|
||||
_pathId = creature.GetWaypointPath();
|
||||
|
||||
_path = Global.WaypointMgr.GetPath(_pathId);
|
||||
}
|
||||
|
||||
if (_path == null)
|
||||
{
|
||||
// No path id found for entry
|
||||
Log.outError(LogFilter.Sql, $"WaypointMovementGenerator.LoadPath: creature {creature.GetName()} ({creature.GetGUID()} DB GUID: {creature.GetSpawnId()}) doesn't have waypoint path id: {_pathId}");
|
||||
return;
|
||||
}
|
||||
|
||||
_nextMoveTime.Reset(1000);
|
||||
}
|
||||
|
||||
public override void DoInitialize(Creature creature)
|
||||
{
|
||||
_done = false;
|
||||
LoadPath(creature);
|
||||
}
|
||||
|
||||
public override void DoFinalize(Creature creature)
|
||||
@@ -51,110 +75,75 @@ namespace Game.Movement
|
||||
creature.SetWalk(false);
|
||||
}
|
||||
|
||||
public override void DoInitialize(Creature creature)
|
||||
public override void DoReset(Creature creature)
|
||||
{
|
||||
LoadPath(creature);
|
||||
creature.AddUnitState(UnitState.Roaming | UnitState.RoamingMove);
|
||||
if (!_done && CanMove(creature))
|
||||
StartMoveNow(creature);
|
||||
else if (_done)
|
||||
{
|
||||
// mimic IdleMovementGenerator
|
||||
if (!creature.IsStopped())
|
||||
creature.StopMoving();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool DoUpdate(Creature creature, uint time_diff)
|
||||
void OnArrived(Creature creature)
|
||||
{
|
||||
// Waypoint movement can be switched on/off
|
||||
// This is quite handy for escort quests and other stuff
|
||||
if (creature.HasUnitState(UnitState.NotMove))
|
||||
if (_path == null || _path.nodes.Empty())
|
||||
return;
|
||||
|
||||
WaypointNode waypoint = _path.nodes.ElementAt((int)_currentNode);
|
||||
if (waypoint.delay != 0)
|
||||
{
|
||||
creature.ClearUnitState(UnitState.RoamingMove);
|
||||
return true;
|
||||
_nextMoveTime.Reset((int)waypoint.delay);
|
||||
}
|
||||
|
||||
// prevent a crash at empty waypoint path.
|
||||
if (path == null || path.Empty())
|
||||
return false;
|
||||
|
||||
if (Stopped())
|
||||
if (waypoint.eventId != 0 && RandomHelper.URand(0, 99) < waypoint.eventChance)
|
||||
{
|
||||
if (CanMove((int)time_diff))
|
||||
return StartMove(creature);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set home position at place on waypoint movement.
|
||||
if (creature.GetTransGUID().IsEmpty())
|
||||
creature.SetHomePosition(creature.GetPosition());
|
||||
|
||||
if (creature.IsStopped())
|
||||
Stop(WorldConfig.GetIntValue(WorldCfg.CreatureStopForPlayer));
|
||||
else if (creature.MoveSpline.Finalized())
|
||||
{
|
||||
OnArrived(creature);
|
||||
return StartMove(creature);
|
||||
}
|
||||
Log.outDebug(LogFilter.MapsScript, $"Creature movement start script {waypoint.eventId} at point {_currentNode} for {creature.GetGUID()}.");
|
||||
creature.ClearUnitState(UnitState.RoamingMove);
|
||||
creature.GetMap().ScriptsStart(ScriptsType.Waypoint, waypoint.eventId, creature, null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MovementInform(Creature creature)
|
||||
{
|
||||
// inform AI
|
||||
if (creature.IsAIEnabled)
|
||||
creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, currentNode);
|
||||
{
|
||||
creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode);
|
||||
|
||||
Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.OnArrived: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})");
|
||||
creature.GetAI().WaypointReached(_path.nodes[_currentNode].id, _path.id);
|
||||
}
|
||||
|
||||
public override bool GetResetPosition(Unit u, out float x, out float y, out float z)
|
||||
{
|
||||
x = y = z = 0;
|
||||
// prevent a crash at empty waypoint path.
|
||||
if (path == null || path.Empty())
|
||||
return false;
|
||||
|
||||
var node = path.LookupByIndex((int)currentNode);
|
||||
x = node.x;
|
||||
y = node.y;
|
||||
z = node.z;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop(int time)
|
||||
{
|
||||
nextMoveTime.Reset(time);
|
||||
}
|
||||
|
||||
bool Stopped()
|
||||
{
|
||||
return !nextMoveTime.Passed();
|
||||
}
|
||||
|
||||
bool CanMove(int diff)
|
||||
{
|
||||
nextMoveTime.Update(diff);
|
||||
return nextMoveTime.Passed();
|
||||
}
|
||||
|
||||
void StartMoveNow(Creature creature)
|
||||
{
|
||||
nextMoveTime.Reset(0);
|
||||
StartMove(creature);
|
||||
creature.UpdateWaypointID((uint)_currentNode);
|
||||
}
|
||||
|
||||
bool StartMove(Creature creature)
|
||||
{
|
||||
if (path == null || path.Empty())
|
||||
return false;
|
||||
|
||||
if (Stopped())
|
||||
if (!creature || !creature.IsAlive())
|
||||
return true;
|
||||
|
||||
if (_done || _path == null || _path.nodes.Empty())
|
||||
return true;
|
||||
|
||||
// if the owner is the leader of its formation, check members status
|
||||
if (creature.IsFormationLeader() && !creature.IsFormationLeaderMoveAllowed())
|
||||
{
|
||||
_nextMoveTime.Reset(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool transportPath = creature.GetTransport() != null;
|
||||
|
||||
if (isArrivalDone)
|
||||
if (_isArrivalDone)
|
||||
{
|
||||
if ((currentNode == path.Count - 1) && !repeating) // If that's our last waypoint
|
||||
if ((_currentNode == _path.nodes.Count - 1) && !_repeating) // If that's our last waypoint
|
||||
{
|
||||
WaypointData waypoint = path.LookupByIndex((int)currentNode);
|
||||
WaypointNode lastWaypoint = _path.nodes.ElementAt(_currentNode);
|
||||
|
||||
float x = waypoint.x;
|
||||
float y = waypoint.y;
|
||||
float z = waypoint.z;
|
||||
float x = lastWaypoint.x;
|
||||
float y = lastWaypoint.y;
|
||||
float z = lastWaypoint.z;
|
||||
float o = creature.GetOrientation();
|
||||
|
||||
if (!transportPath)
|
||||
@@ -173,21 +162,28 @@ namespace Game.Movement
|
||||
transportPath = false;
|
||||
// else if (vehicle) - this should never happen, vehicle offsets are const
|
||||
}
|
||||
|
||||
creature.GetMotionMaster().Initialize();
|
||||
return false;
|
||||
_done = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentNode = (uint)((currentNode + 1) % path.Count);
|
||||
_currentNode = (_currentNode + 1) % _path.nodes.Count;
|
||||
|
||||
// inform AI
|
||||
if (creature.IsAIEnabled)
|
||||
{
|
||||
Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})");
|
||||
creature.GetAI().WaypointStarted(_path.nodes[(int)_currentNode].id, _path.id);
|
||||
}
|
||||
}
|
||||
|
||||
var node = path.LookupByIndex((int)currentNode);
|
||||
WaypointNode waypoint = _path.nodes.ElementAt(_currentNode);
|
||||
Position formationDest = new Position(waypoint.x, waypoint.y, waypoint.z, (waypoint.orientation != 0 && waypoint.delay != 0) ? waypoint.orientation : 0.0f);
|
||||
|
||||
isArrivalDone = false;
|
||||
_isArrivalDone = false;
|
||||
_recalculateSpeed = false;
|
||||
|
||||
creature.AddUnitState(UnitState.RoamingMove);
|
||||
|
||||
Position formationDest = new Position(node.x, node.y, node.z, (node.orientation != 0 && node.delay != 0) ? node.orientation : 0.0f);
|
||||
MoveSplineInit init = new MoveSplineInit(creature);
|
||||
|
||||
//! If creature is on transport, we assume waypoints set in DB are already transport offsets
|
||||
@@ -205,13 +201,13 @@ namespace Game.Movement
|
||||
|
||||
//! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
|
||||
//! but formationDest contains global coordinates
|
||||
init.MoveTo(node.x, node.y, node.z);
|
||||
init.MoveTo(waypoint.x, waypoint.y, waypoint.z);
|
||||
|
||||
//! Accepts angles such as 0.00001 and -0.00001, 0 must be ignored, default value in waypoint table
|
||||
if (node.orientation != 0 && node.delay != 0)
|
||||
init.SetFacing(node.orientation);
|
||||
if (waypoint.orientation != 0 && waypoint.delay != 0)
|
||||
init.SetFacing(waypoint.orientation);
|
||||
|
||||
switch (node.moveType)
|
||||
switch (waypoint.moveType)
|
||||
{
|
||||
case WaypointMoveType.Land:
|
||||
init.SetAnimation(AnimType.ToGround);
|
||||
@@ -229,82 +225,146 @@ namespace Game.Movement
|
||||
|
||||
init.Launch();
|
||||
|
||||
//Call for creature group update
|
||||
if (creature.GetFormation() != null && creature.GetFormation().GetLeader() == creature)
|
||||
creature.GetFormation().LeaderMoveTo(formationDest, node.id, (uint)node.moveType, (node.orientation != 0 && node.delay != 0) ? true : false);
|
||||
// inform formation
|
||||
creature.SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation != 0 && waypoint.delay != 0) ? true : false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadPath(Creature creature)
|
||||
public override bool DoUpdate(Creature creature, uint diff)
|
||||
{
|
||||
if (pathId == 0)
|
||||
pathId = creature.GetWaypointPath();
|
||||
if (!creature || !creature.IsAlive())
|
||||
return true;
|
||||
|
||||
path = Global.WaypointMgr.GetPath(pathId);
|
||||
if (_done || _path == null || _path.nodes.Empty())
|
||||
return true;
|
||||
|
||||
if (path == null)
|
||||
if (_stalled || creature.HasUnitState(UnitState.NotMove) || creature.IsMovementPreventedByCasting())
|
||||
{
|
||||
// No movement found for entry
|
||||
Log.outError(LogFilter.ScriptsAi, "WaypointMovementGenerator.LoadPath: creature {0} (Entry: {1} GUID: {2}) doesn't have waypoint path id: {3}", creature.GetName(), creature.GetEntry(), creature.GetGUID().ToString(), pathId);
|
||||
return;
|
||||
creature.StopMoving();
|
||||
return true;
|
||||
}
|
||||
|
||||
StartMoveNow(creature);
|
||||
|
||||
if (!_nextMoveTime.Passed())
|
||||
{
|
||||
_nextMoveTime.Update((int)diff);
|
||||
if (_nextMoveTime.Passed())
|
||||
return StartMoveNow(creature);
|
||||
}
|
||||
|
||||
void OnArrived(Creature creature)
|
||||
else
|
||||
{
|
||||
if (path == null || path.Empty())
|
||||
return;
|
||||
// Set home position at place on waypoint movement.
|
||||
if (creature.GetTransGUID().IsEmpty())
|
||||
creature.SetHomePosition(creature.GetPosition());
|
||||
|
||||
if (isArrivalDone)
|
||||
return;
|
||||
|
||||
isArrivalDone = true;
|
||||
|
||||
var current = path.LookupByIndex((int)currentNode);
|
||||
|
||||
if (current.eventId != 0 && RandomHelper.URand(0, 99) < current.eventChance)
|
||||
if (creature.MoveSpline.Finalized())
|
||||
{
|
||||
Log.outDebug(LogFilter.Unit, "Creature movement start script {0} at point {1} for {2}.", current.eventId, currentNode, creature.GetGUID());
|
||||
creature.ClearUnitState(UnitState.RoamingMove);
|
||||
creature.GetMap().ScriptsStart(ScriptsType.Waypoint, current.eventId, creature, null);
|
||||
OnArrived(creature);
|
||||
_isArrivalDone = true;
|
||||
|
||||
if (_nextMoveTime.Passed())
|
||||
return StartMove(creature);
|
||||
}
|
||||
|
||||
// Inform script
|
||||
MovementInform(creature);
|
||||
creature.UpdateWaypointID(currentNode);
|
||||
|
||||
if (current.delay != 0)
|
||||
else if (_recalculateSpeed)
|
||||
{
|
||||
creature.ClearUnitState(UnitState.RoamingMove);
|
||||
Stop((int)current.delay);
|
||||
if (_nextMoveTime.Passed())
|
||||
StartMove(creature);
|
||||
}
|
||||
}
|
||||
|
||||
public override MovementGeneratorType GetMovementGeneratorType()
|
||||
{
|
||||
return MovementGeneratorType.Waypoint;
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetCurrentNode() { return currentNode; }
|
||||
void MovementInform(Creature creature)
|
||||
{
|
||||
if (creature.IsAIEnabled)
|
||||
creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode);
|
||||
}
|
||||
|
||||
TimeTrackerSmall nextMoveTime;
|
||||
public override bool GetResetPosition(Unit u, out float x, out float y, out float z)
|
||||
{
|
||||
x = y = z = 0;
|
||||
// prevent a crash at empty waypoint path.
|
||||
// prevent a crash at empty waypoint path.
|
||||
if (_path == null || _path.nodes.Empty())
|
||||
return false;
|
||||
|
||||
bool isArrivalDone;
|
||||
uint pathId;
|
||||
bool repeating;
|
||||
List<WaypointData> path;
|
||||
uint currentNode;
|
||||
WaypointNode waypoint = _path.nodes.ElementAt(_currentNode);
|
||||
|
||||
x = waypoint.x;
|
||||
y = waypoint.y;
|
||||
z = waypoint.z;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pause(uint timer = 0)
|
||||
{
|
||||
_stalled = timer != 0 ? false : true;
|
||||
_nextMoveTime.Reset(timer != 0 ? (int)timer : 1);
|
||||
}
|
||||
|
||||
public override void Resume(uint overrideTimer = 0)
|
||||
{
|
||||
_stalled = false;
|
||||
if (overrideTimer != 0)
|
||||
_nextMoveTime.Reset((int)overrideTimer);
|
||||
}
|
||||
|
||||
bool CanMove(Creature creature)
|
||||
{
|
||||
return _nextMoveTime.Passed() && !creature.HasUnitState(UnitState.NotMove) && !creature.IsMovementPreventedByCasting();
|
||||
}
|
||||
|
||||
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Waypoint; }
|
||||
|
||||
public override void UnitSpeedChanged() { _recalculateSpeed = true; }
|
||||
|
||||
bool StartMoveNow(Creature creature)
|
||||
{
|
||||
_nextMoveTime.Reset(0);
|
||||
return StartMove(creature);
|
||||
}
|
||||
|
||||
TimeTrackerSmall _nextMoveTime;
|
||||
bool _recalculateSpeed;
|
||||
bool _isArrivalDone;
|
||||
uint _pathId;
|
||||
bool _repeating;
|
||||
bool _loadedFromDB;
|
||||
bool _stalled;
|
||||
bool _done;
|
||||
|
||||
WaypointPath _path;
|
||||
int _currentNode;
|
||||
}
|
||||
|
||||
public class FlightPathMovementGenerator : MovementGeneratorMedium<Player>
|
||||
{
|
||||
uint GetPathAtMapEnd()
|
||||
{
|
||||
if (_currentNode >= _path.Count)
|
||||
return (uint)_path.Count;
|
||||
|
||||
uint curMapId = _path[_currentNode].ContinentID;
|
||||
for (int i = _currentNode; i < _path.Count; ++i)
|
||||
{
|
||||
if (_path[i].ContinentID != curMapId)
|
||||
return (uint)i;
|
||||
}
|
||||
|
||||
return (uint)_path.Count;
|
||||
}
|
||||
|
||||
bool IsNodeIncludedInShortenedPath(TaxiPathNodeRecord p1, TaxiPathNodeRecord p2)
|
||||
{
|
||||
return p1.ContinentID != p2.ContinentID || Math.Pow(p1.Loc.X - p2.Loc.X, 2) + Math.Pow(p1.Loc.Y - p2.Loc.Y, 2) > (40.0f * 40.0f);
|
||||
}
|
||||
|
||||
public void LoadPath(Player player, uint startNode = 0)
|
||||
{
|
||||
i_path.Clear();
|
||||
i_currentNode = (int)startNode;
|
||||
_path.Clear();
|
||||
_currentNode = (int)startNode;
|
||||
_pointsForPathSwitch.Clear();
|
||||
var taxi = player.m_taxi.GetPath();
|
||||
float discount = player.GetReputationPriceDiscount(player.m_taxi.GetFlightMasterFactionTemplate());
|
||||
@@ -324,24 +384,24 @@ namespace Game.Movement
|
||||
bool passedPreviousSegmentProximityCheck = false;
|
||||
for (uint i = 0; i < nodes.Length; ++i)
|
||||
{
|
||||
if (passedPreviousSegmentProximityCheck || src == 0 || i_path.Empty() || IsNodeIncludedInShortenedPath(i_path.Last(), nodes[i]))
|
||||
if (passedPreviousSegmentProximityCheck || src == 0 || _path.Empty() || IsNodeIncludedInShortenedPath(_path.Last(), nodes[i]))
|
||||
{
|
||||
if ((src == 0 || (IsNodeIncludedInShortenedPath(start, nodes[i]) && i >= 2)) &&
|
||||
(dst == taxi.Count - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && i < nodes.Length - 1)))
|
||||
{
|
||||
passedPreviousSegmentProximityCheck = true;
|
||||
i_path.Add(nodes[i]);
|
||||
_path.Add(nodes[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
i_path.RemoveAt(i_path.Count - 1);
|
||||
_path.RemoveAt(_path.Count - 1);
|
||||
_pointsForPathSwitch[_pointsForPathSwitch.Count - 1].PathIndex -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pointsForPathSwitch.Add(new TaxiNodeChangeInfo((uint)(i_path.Count - 1), (long)Math.Ceiling(cost * discount)));
|
||||
_pointsForPathSwitch.Add(new TaxiNodeChangeInfo((uint)(_path.Count - 1), (long)Math.Ceiling(cost * discount)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +444,7 @@ namespace Game.Movement
|
||||
init.args.path = new Vector3[end];
|
||||
for (int i = (int)GetCurrentNode(); i != end; ++i)
|
||||
{
|
||||
Vector3 vertice = new Vector3(i_path[i].Loc.X, i_path[i].Loc.Y, i_path[i].Loc.Z);
|
||||
Vector3 vertice = new Vector3(_path[i].Loc.X, _path[i].Loc.Y, _path[i].Loc.Z);
|
||||
init.args.path[i] = vertice;
|
||||
}
|
||||
init.SetFirstPointId((int)GetCurrentNode());
|
||||
@@ -399,13 +459,13 @@ namespace Game.Movement
|
||||
public override bool DoUpdate(Player player, uint time_diff)
|
||||
{
|
||||
uint pointId = (uint)player.MoveSpline.CurrentPathIdx();
|
||||
if (pointId > i_currentNode)
|
||||
if (pointId > _currentNode)
|
||||
{
|
||||
bool departureEvent = true;
|
||||
do
|
||||
{
|
||||
DoEventIfAny(player, i_path[i_currentNode], departureEvent);
|
||||
while (!_pointsForPathSwitch.Empty() && _pointsForPathSwitch[0].PathIndex <= i_currentNode)
|
||||
DoEventIfAny(player, _path[_currentNode], departureEvent);
|
||||
while (!_pointsForPathSwitch.Empty() && _pointsForPathSwitch[0].PathIndex <= _currentNode)
|
||||
{
|
||||
_pointsForPathSwitch.RemoveAt(0);
|
||||
player.m_taxi.NextTaxiDestination();
|
||||
@@ -416,31 +476,31 @@ namespace Game.Movement
|
||||
}
|
||||
}
|
||||
|
||||
if (pointId == i_currentNode)
|
||||
if (pointId == _currentNode)
|
||||
break;
|
||||
|
||||
if (i_currentNode == _preloadTargetNode)
|
||||
if (_currentNode == _preloadTargetNode)
|
||||
PreloadEndGrid();
|
||||
i_currentNode += (departureEvent ? 1 : 0);
|
||||
_currentNode += (departureEvent ? 1 : 0);
|
||||
departureEvent = !departureEvent;
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
return i_currentNode < (i_path.Count - 1);
|
||||
return _currentNode < (_path.Count - 1);
|
||||
}
|
||||
|
||||
public void SetCurrentNodeAfterTeleport()
|
||||
{
|
||||
if (i_path.Empty() || i_currentNode >= i_path.Count)
|
||||
if (_path.Empty() || _currentNode >= _path.Count)
|
||||
return;
|
||||
|
||||
uint map0 = i_path[i_currentNode].ContinentID;
|
||||
for (int i = i_currentNode + 1; i < i_path.Count; ++i)
|
||||
uint map0 = _path[_currentNode].ContinentID;
|
||||
for (int i = _currentNode + 1; i < _path.Count; ++i)
|
||||
{
|
||||
if (i_path[i].ContinentID != map0)
|
||||
if (_path[i].ContinentID != map0)
|
||||
{
|
||||
i_currentNode = i;
|
||||
_currentNode = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -459,7 +519,7 @@ namespace Game.Movement
|
||||
|
||||
bool GetResetPos(Player player, out float x, out float y, out float z)
|
||||
{
|
||||
TaxiPathNodeRecord node = i_path[i_currentNode];
|
||||
TaxiPathNodeRecord node = _path[_currentNode];
|
||||
x = node.Loc.X;
|
||||
y = node.Loc.Y;
|
||||
z = node.Loc.Z;
|
||||
@@ -468,11 +528,11 @@ namespace Game.Movement
|
||||
|
||||
void InitEndGridInfo()
|
||||
{
|
||||
int nodeCount = i_path.Count; //! Number of nodes in path.
|
||||
_endMapId = i_path[nodeCount - 1].ContinentID; //! MapId of last node
|
||||
int nodeCount = _path.Count; //! Number of nodes in path.
|
||||
_endMapId = _path[nodeCount - 1].ContinentID; //! MapId of last node
|
||||
_preloadTargetNode = (uint)nodeCount - 3;
|
||||
_endGridX = i_path[nodeCount - 1].Loc.X;
|
||||
_endGridY = i_path[nodeCount - 1].Loc.Y;
|
||||
_endGridX = _path[nodeCount - 1].Loc.X;
|
||||
_endGridY = _path[nodeCount - 1].Loc.Y;
|
||||
}
|
||||
|
||||
void PreloadEndGrid()
|
||||
@@ -483,51 +543,30 @@ namespace Game.Movement
|
||||
// Load the grid
|
||||
if (endMap != null)
|
||||
{
|
||||
Log.outInfo(LogFilter.Server, "Preloading grid ({0}, {1}) for map {2} at node index {3}/{4}", _endGridX, _endGridY, _endMapId, _preloadTargetNode, i_path.Count - 1);
|
||||
Log.outInfo(LogFilter.Server, "Preloading grid ({0}, {1}) for map {2} at node index {3}/{4}", _endGridX, _endGridY, _endMapId, _preloadTargetNode, _path.Count - 1);
|
||||
endMap.LoadGrid(_endGridX, _endGridY);
|
||||
}
|
||||
else
|
||||
Log.outInfo(LogFilter.Server, "Unable to determine map to preload flightmaster grid");
|
||||
}
|
||||
|
||||
uint GetPathAtMapEnd()
|
||||
{
|
||||
if (i_currentNode >= i_path.Count)
|
||||
return (uint)i_path.Count;
|
||||
|
||||
uint curMapId = i_path[i_currentNode].ContinentID;
|
||||
for (int i = i_currentNode; i < i_path.Count; ++i)
|
||||
{
|
||||
if (i_path[i].ContinentID != curMapId)
|
||||
return (uint)i;
|
||||
}
|
||||
|
||||
return (uint)i_path.Count;
|
||||
}
|
||||
|
||||
bool IsNodeIncludedInShortenedPath(TaxiPathNodeRecord p1, TaxiPathNodeRecord p2)
|
||||
{
|
||||
return p1.ContinentID != p2.ContinentID || Math.Pow(p1.Loc.X - p2.Loc.X, 2) + Math.Pow(p1.Loc.Y - p2.Loc.Y, 2) > (40.0f * 40.0f);
|
||||
}
|
||||
|
||||
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Flight; }
|
||||
|
||||
public List<TaxiPathNodeRecord> GetPath() { return i_path; }
|
||||
public List<TaxiPathNodeRecord> GetPath() { return _path; }
|
||||
|
||||
bool HasArrived() { return (i_currentNode >= i_path.Count); }
|
||||
bool HasArrived() { return (_currentNode >= _path.Count); }
|
||||
|
||||
public void SkipCurrentNode() { ++i_currentNode; }
|
||||
|
||||
public uint GetCurrentNode() { return (uint)i_currentNode; }
|
||||
public void SkipCurrentNode() { ++_currentNode; }
|
||||
|
||||
public uint GetCurrentNode() { return (uint)_currentNode; }
|
||||
|
||||
float _endGridX; //! X coord of last node location
|
||||
float _endGridY; //! Y coord of last node location
|
||||
uint _endMapId; //! map Id of last node location
|
||||
uint _preloadTargetNode; //! node index where preloading starts
|
||||
|
||||
int i_currentNode;
|
||||
List<TaxiPathNodeRecord> i_path = new List<TaxiPathNodeRecord>();
|
||||
List<TaxiPathNodeRecord> _path = new List<TaxiPathNodeRecord>();
|
||||
int _currentNode;
|
||||
List<TaxiNodeChangeInfo> _pointsForPathSwitch = new List<TaxiNodeChangeInfo>(); //! node indexes and costs where TaxiPath changes
|
||||
|
||||
class TaxiNodeChangeInfo
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Game.Movement
|
||||
DirectClean(reset);
|
||||
}
|
||||
|
||||
void Clear(MovementSlot slot)
|
||||
public void Clear(MovementSlot slot)
|
||||
{
|
||||
if (Empty() || slot >= MovementSlot.Max)
|
||||
return;
|
||||
@@ -593,12 +593,17 @@ namespace Game.Movement
|
||||
StartMovement(new DistractMovementGenerator(timer), MovementSlot.Controlled);
|
||||
}
|
||||
|
||||
public void MovePath(uint path_id, bool repeatable)
|
||||
public void MovePath(uint pathId, bool repeatable)
|
||||
{
|
||||
if (path_id == 0)
|
||||
if (pathId == 0)
|
||||
return;
|
||||
|
||||
StartMovement(new WaypointMovementGenerator(path_id, repeatable), MovementSlot.Idle);
|
||||
StartMovement(new WaypointMovementGenerator(pathId, repeatable), MovementSlot.Idle);
|
||||
}
|
||||
|
||||
public void MovePath(WaypointPath path, bool repeatable)
|
||||
{
|
||||
StartMovement(new WaypointMovementGenerator(path, repeatable), MovementSlot.Idle);
|
||||
}
|
||||
|
||||
void MoveRotate(uint time, RotateDirection direction)
|
||||
@@ -609,7 +614,7 @@ namespace Game.Movement
|
||||
StartMovement(new RotateMovementGenerator(time, direction), MovementSlot.Active);
|
||||
}
|
||||
|
||||
public void MoveFormation(uint id, Position destination, uint moveType, bool forceRun = false, bool forceOrientation = false)
|
||||
public void MoveFormation(uint id, Position destination, WaypointMoveType moveType, bool forceRun = false, bool forceOrientation = false)
|
||||
{
|
||||
if (_owner.GetTypeId() == TypeId.Unit)
|
||||
StartMovement(new FormationMovementGenerator(id, destination, moveType, forceRun, forceOrientation), MovementSlot.Active);
|
||||
|
||||
@@ -23,10 +23,7 @@ namespace Game
|
||||
{
|
||||
public sealed class WaypointManager : Singleton<WaypointManager>
|
||||
{
|
||||
WaypointManager()
|
||||
{
|
||||
_waypointStore = new MultiMap<uint, WaypointData>();
|
||||
}
|
||||
WaypointManager() { }
|
||||
|
||||
public void Load()
|
||||
{
|
||||
@@ -55,29 +52,35 @@ namespace Game
|
||||
GridDefines.NormalizeMapCoord(ref x);
|
||||
GridDefines.NormalizeMapCoord(ref y);
|
||||
|
||||
WaypointData wp = new WaypointData();
|
||||
wp.id = result.Read<uint>(1);
|
||||
wp.x = x;
|
||||
wp.y = y;
|
||||
wp.z = z;
|
||||
wp.orientation = o;
|
||||
wp.moveType = (WaypointMoveType)result.Read<uint>(6);
|
||||
WaypointNode waypoint = new WaypointNode();
|
||||
waypoint.id = result.Read<uint>(1);
|
||||
waypoint.x = x;
|
||||
waypoint.y = y;
|
||||
waypoint.z = z;
|
||||
waypoint.orientation = o;
|
||||
waypoint.moveType = (WaypointMoveType)result.Read<uint>(6);
|
||||
|
||||
if (wp.moveType >= WaypointMoveType.Max)
|
||||
if (waypoint.moveType >= WaypointMoveType.Max)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Waypoint {0} in waypoint_data has invalid move_type, ignoring", wp.id);
|
||||
Log.outError(LogFilter.Sql, $"Waypoint {waypoint.id} in waypoint_data has invalid move_type, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
||||
wp.delay = result.Read<uint>(7);
|
||||
wp.eventId = result.Read<uint>(8);
|
||||
wp.eventChance = result.Read<byte>(9);
|
||||
waypoint.delay = result.Read<uint>(7);
|
||||
waypoint.eventId = result.Read<uint>(8);
|
||||
waypoint.eventChance = result.Read<byte>(9);
|
||||
|
||||
if (!_waypointStore.ContainsKey(pathId))
|
||||
_waypointStore[pathId] = new WaypointPath();
|
||||
|
||||
WaypointPath path = _waypointStore[pathId];
|
||||
path.id = pathId;
|
||||
path.nodes.Add(waypoint);
|
||||
|
||||
_waypointStore.Add(pathId, wp);
|
||||
++count;
|
||||
} while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} waypoints in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} waypoints in {Time.GetMSTimeDiffToNow(oldMSTime)} ms");
|
||||
}
|
||||
|
||||
public void ReloadPath(uint id)
|
||||
@@ -91,6 +94,7 @@ namespace Game
|
||||
if (result.IsEmpty())
|
||||
return;
|
||||
|
||||
List<WaypointNode> values = new List<WaypointNode>();
|
||||
do
|
||||
{
|
||||
float x = result.Read<float>(1);
|
||||
@@ -101,42 +105,43 @@ namespace Game
|
||||
GridDefines.NormalizeMapCoord(ref x);
|
||||
GridDefines.NormalizeMapCoord(ref y);
|
||||
|
||||
WaypointData wp = new WaypointData();
|
||||
wp.id = result.Read<uint>(0);
|
||||
wp.x = x;
|
||||
wp.y = y;
|
||||
wp.z = z;
|
||||
wp.orientation = o;
|
||||
wp.moveType = (WaypointMoveType)result.Read<uint>(5);
|
||||
WaypointNode waypoint = new WaypointNode();
|
||||
waypoint.id = result.Read<uint>(0);
|
||||
waypoint.x = x;
|
||||
waypoint.y = y;
|
||||
waypoint.z = z;
|
||||
waypoint.orientation = o;
|
||||
waypoint.moveType = (WaypointMoveType)result.Read<uint>(5);
|
||||
|
||||
if (wp.moveType >= WaypointMoveType.Max)
|
||||
if (waypoint.moveType >= WaypointMoveType.Max)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Waypoint {0} in waypoint_data has invalid move_type, ignoring", wp.id);
|
||||
Log.outError(LogFilter.Sql, $"Waypoint {waypoint.id} in waypoint_data has invalid move_type, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
||||
wp.delay = result.Read<uint>(6);
|
||||
wp.eventId = result.Read<uint>(7);
|
||||
wp.eventChance = result.Read<byte>(8);
|
||||
|
||||
_waypointStore.Add(id, wp);
|
||||
waypoint.delay = result.Read<uint>(6);
|
||||
waypoint.eventId = result.Read<uint>(7);
|
||||
waypoint.eventChance = result.Read<byte>(8);
|
||||
|
||||
values.Add(waypoint);
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
_waypointStore[id] = new WaypointPath(id, values);
|
||||
}
|
||||
|
||||
public List<WaypointData> GetPath(uint id)
|
||||
public WaypointPath GetPath(uint id)
|
||||
{
|
||||
return _waypointStore.LookupByKey(id);
|
||||
}
|
||||
|
||||
MultiMap<uint, WaypointData> _waypointStore;
|
||||
Dictionary<uint, WaypointPath> _waypointStore = new Dictionary<uint, WaypointPath>();
|
||||
}
|
||||
|
||||
public class WaypointData
|
||||
public class WaypointNode
|
||||
{
|
||||
public WaypointData() { moveType = WaypointMoveType.Run; }
|
||||
public WaypointData(uint _id, float _x, float _y, float _z, float _orientation = 0.0f, uint _delay = 0)
|
||||
public WaypointNode() { moveType = WaypointMoveType.Run; }
|
||||
public WaypointNode(uint _id, float _x, float _y, float _z, float _orientation = 0.0f, uint _delay = 0)
|
||||
{
|
||||
id = _id;
|
||||
x = _x;
|
||||
@@ -157,6 +162,19 @@ namespace Game
|
||||
public byte eventChance;
|
||||
}
|
||||
|
||||
public class WaypointPath
|
||||
{
|
||||
public WaypointPath() { }
|
||||
public WaypointPath(uint _id, List<WaypointNode> _nodes)
|
||||
{
|
||||
id = _id;
|
||||
nodes = _nodes;
|
||||
}
|
||||
|
||||
public List<WaypointNode> nodes = new List<WaypointNode>();
|
||||
public uint id;
|
||||
}
|
||||
|
||||
public enum WaypointMoveType
|
||||
{
|
||||
Walk,
|
||||
|
||||
@@ -197,22 +197,22 @@ namespace Game.Scripting
|
||||
LoadScriptWaypoints();
|
||||
LoadScriptSplineChains();
|
||||
}
|
||||
|
||||
void LoadScriptWaypoints()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
// Drop Existing Waypoint list
|
||||
_waypointStore.Clear();
|
||||
|
||||
m_mPointMoveMap.Clear();
|
||||
|
||||
ulong uiCreatureCount = 0;
|
||||
ulong entryCount = 0;
|
||||
|
||||
// Load Waypoints
|
||||
SQLResult result = DB.World.Query("SELECT COUNT(entry) FROM script_waypoint GROUP BY entry");
|
||||
if (!result.IsEmpty())
|
||||
uiCreatureCount = result.Read<uint>(0);
|
||||
entryCount = result.Read<uint>(0);
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loading Script Waypoints for {0} creature(s)...", uiCreatureCount);
|
||||
Log.outInfo(LogFilter.ServerLoading, $"Loading Script Waypoints for {entryCount} creature(s)...");
|
||||
|
||||
// 0 1 2 3 4 5
|
||||
result = DB.World.Query("SELECT entry, pointid, location_x, location_y, location_z, waittime FROM script_waypoint ORDER BY pointid");
|
||||
@@ -227,28 +227,30 @@ namespace Game.Scripting
|
||||
|
||||
do
|
||||
{
|
||||
ScriptPointMove temp = new ScriptPointMove();
|
||||
uint entry = result.Read<uint>(0);
|
||||
uint id = result.Read<uint>(1);
|
||||
float x = result.Read<float>(2);
|
||||
float y = result.Read<float>(3);
|
||||
float z = result.Read<float>(4);
|
||||
uint waitTime = result.Read<uint>(5);
|
||||
|
||||
temp.uiCreatureEntry = result.Read<uint>(0);
|
||||
uint uiEntry = temp.uiCreatureEntry;
|
||||
temp.uiPointId = result.Read<uint>(1);
|
||||
temp.fX = result.Read<float>(2);
|
||||
temp.fY = result.Read<float>(3);
|
||||
temp.fZ = result.Read<float>(4);
|
||||
temp.uiWaitTime = result.Read<uint>(5);
|
||||
|
||||
CreatureTemplate pCInfo = Global.ObjectMgr.GetCreatureTemplate(temp.uiCreatureEntry);
|
||||
|
||||
if (pCInfo == null)
|
||||
CreatureTemplate info = Global.ObjectMgr.GetCreatureTemplate(entry);
|
||||
if (info == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for non-existant creature entry {0}", temp.uiCreatureEntry);
|
||||
Log.outError(LogFilter.Sql, $"SystemMgr: DB table script_waypoint has waypoint for non-existant creature entry {entry}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pCInfo.ScriptID == 0)
|
||||
Log.outError(LogFilter.Sql, "TSCR: DB table script_waypoint has waypoint for creature entry {0}, but creature does not have ScriptName defined and then useless.", temp.uiCreatureEntry);
|
||||
if (info.ScriptID == 0)
|
||||
Log.outError(LogFilter.Sql, $"SystemMgr: DB table script_waypoint has waypoint for creature entry {entry}, but creature does not have ScriptName defined and then useless.");
|
||||
|
||||
if (!_waypointStore.ContainsKey(entry))
|
||||
_waypointStore[entry] = new WaypointPath();
|
||||
|
||||
WaypointPath path = _waypointStore[entry];
|
||||
path.id = entry;
|
||||
path.nodes.Add(new WaypointNode(id, x, y, z, 0.0f, waitTime));
|
||||
|
||||
m_mPointMoveMap.Add(uiEntry, temp);
|
||||
++count;
|
||||
}
|
||||
while (result.NextRow());
|
||||
@@ -256,6 +258,7 @@ namespace Game.Scripting
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Script Waypoint nodes in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
|
||||
}
|
||||
|
||||
void LoadScriptSplineChains()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
@@ -332,15 +335,22 @@ namespace Game.Scripting
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded spline chain data for {0} chains, consisting of {1} splines with {2} waypoints in {3} ms", chainCount, splineCount, wpCount, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
}
|
||||
|
||||
public void FillSpellSummary()
|
||||
{
|
||||
UnitAI.FillAISpellInfo();
|
||||
}
|
||||
|
||||
public WaypointPath GetPath(uint creatureEntry)
|
||||
{
|
||||
return _waypointStore.LookupByKey(creatureEntry);
|
||||
}
|
||||
|
||||
public List<SplineChainLink> GetSplineChain(Creature who, ushort chainId)
|
||||
{
|
||||
return GetSplineChain(who.GetEntry(), chainId);
|
||||
}
|
||||
|
||||
List<SplineChainLink> GetSplineChain(uint entry, ushort chainId)
|
||||
{
|
||||
return m_mSplineChainsMap.LookupByKey(Tuple.Create(entry, chainId));
|
||||
@@ -1242,11 +1252,6 @@ namespace Game.Scripting
|
||||
GetScriptRegistry<T>().AddScript(script);
|
||||
}
|
||||
|
||||
public List<ScriptPointMove> GetPointMoveList(uint creatureEntry)
|
||||
{
|
||||
return m_mPointMoveMap.LookupByKey(creatureEntry);
|
||||
}
|
||||
|
||||
ScriptRegistry<T> GetScriptRegistry<T>() where T : ScriptObject
|
||||
{
|
||||
if (ScriptStorage.ContainsKey(typeof(T)))
|
||||
@@ -1259,7 +1264,7 @@ namespace Game.Scripting
|
||||
public Dictionary<uint, SpellSummary> spellSummaryStorage = new Dictionary<uint, SpellSummary>();
|
||||
Hashtable ScriptStorage = new Hashtable();
|
||||
|
||||
MultiMap<uint, ScriptPointMove> m_mPointMoveMap = new MultiMap<uint, ScriptPointMove>();
|
||||
Dictionary<uint, WaypointPath> _waypointStore = new Dictionary<uint, WaypointPath>();
|
||||
|
||||
// creature entry + chain ID
|
||||
MultiMap<Tuple<uint, ushort>, SplineChainLink> m_mSplineChainsMap = new MultiMap<Tuple<uint, ushort>, SplineChainLink>(); // spline chains
|
||||
@@ -1348,16 +1353,6 @@ namespace Game.Scripting
|
||||
Dictionary<uint, TValue> ScriptMap = new Dictionary<uint, TValue>();
|
||||
}
|
||||
|
||||
public class ScriptPointMove
|
||||
{
|
||||
public uint uiCreatureEntry;
|
||||
public uint uiPointId;
|
||||
public float fX;
|
||||
public float fY;
|
||||
public float fZ;
|
||||
public uint uiWaitTime;
|
||||
}
|
||||
|
||||
public class SpellSummary
|
||||
{
|
||||
public byte Targets; // set of enum SelectTarget
|
||||
|
||||
@@ -1379,7 +1379,7 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent
|
||||
{
|
||||
public npc_barnes() : base("npc_barnes") { }
|
||||
|
||||
class npc_barnesAI : NpcEscortAI
|
||||
class npc_barnesAI : EscortAI
|
||||
{
|
||||
public npc_barnesAI(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -1418,7 +1418,7 @@ namespace Scripts.EasternKingdoms.Karazhan.OperaEvent
|
||||
|
||||
public override void EnterCombat(Unit who) { }
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
switch (waypointId)
|
||||
{
|
||||
|
||||
@@ -947,7 +947,7 @@ namespace Scripts.EasternKingdoms
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_scarlet_miner : NpcEscortAI
|
||||
class npc_scarlet_miner : EscortAI
|
||||
{
|
||||
public npc_scarlet_miner(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -1012,7 +1012,7 @@ namespace Scripts.EasternKingdoms
|
||||
SetDespawnAtFar(false);
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
switch (waypointId)
|
||||
{
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Scripts.Kalimdor.ZoneAshenvale
|
||||
|
||||
|
||||
[Script]
|
||||
class npc_ruul_snowhoof : NpcEscortAI
|
||||
class npc_ruul_snowhoof : EscortAI
|
||||
{
|
||||
public npc_ruul_snowhoof(Creature creature) : base(creature) { }
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Scripts.Kalimdor.ZoneAshenvale
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
Player player = GetPlayerForEscort();
|
||||
if (!player)
|
||||
@@ -170,7 +170,7 @@ namespace Scripts.Kalimdor.ZoneAshenvale
|
||||
}
|
||||
|
||||
[Script]
|
||||
public class npc_muglash : NpcEscortAI
|
||||
public class npc_muglash : EscortAI
|
||||
{
|
||||
public npc_muglash(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -228,7 +228,7 @@ namespace Scripts.Kalimdor.ZoneAshenvale
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
Player player = GetPlayerForEscort();
|
||||
if (player)
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion
|
||||
}
|
||||
|
||||
[Script]
|
||||
class generic_vehicleAI_toc5 : NpcEscortAI
|
||||
class generic_vehicleAI_toc5 : EscortAI
|
||||
{
|
||||
public generic_vehicleAI_toc5(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -124,7 +124,7 @@ namespace Scripts.Northrend.CrusadersColiseum.TrialOfTheChampion
|
||||
Start(false, true);
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
switch (waypointId)
|
||||
{
|
||||
|
||||
@@ -666,7 +666,7 @@ namespace Scripts.Northrend.IcecrownCitadel
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_crok_scourgebane : NpcEscortAI
|
||||
class npc_crok_scourgebane : EscortAI
|
||||
{
|
||||
public npc_crok_scourgebane(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -748,7 +748,7 @@ namespace Scripts.Northrend.IcecrownCitadel
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
switch (waypointId)
|
||||
{
|
||||
@@ -780,7 +780,7 @@ namespace Scripts.Northrend.IcecrownCitadel
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointStart(uint waypointId)
|
||||
public override void WaypointStarted(uint waypointId, uint pathId)
|
||||
{
|
||||
_currentWPid = waypointId;
|
||||
switch (waypointId)
|
||||
|
||||
@@ -867,7 +867,7 @@ namespace Scripts.Northrend.Ulduar.FlameLeviathan
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_mimirons_inferno : NpcEscortAI
|
||||
class npc_mimirons_inferno : EscortAI
|
||||
{
|
||||
public npc_mimirons_inferno(Creature creature)
|
||||
: base(creature)
|
||||
@@ -877,10 +877,7 @@ namespace Scripts.Northrend.Ulduar.FlameLeviathan
|
||||
me.SetReactState(ReactStates.Passive);
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
{
|
||||
|
||||
}
|
||||
public override void WaypointReached(uint waypointId, uint pathId) { }
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Scripts.Outlands
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_ancestral_wolf : NpcEscortAI
|
||||
class npc_ancestral_wolf : EscortAI
|
||||
{
|
||||
public npc_ancestral_wolf(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -146,7 +146,7 @@ namespace Scripts.Outlands
|
||||
base.MoveInLineOfSight(who);
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
switch (waypointId)
|
||||
{
|
||||
@@ -167,7 +167,7 @@ namespace Scripts.Outlands
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_wounded_blood_elf : NpcEscortAI
|
||||
class npc_wounded_blood_elf : EscortAI
|
||||
{
|
||||
public npc_wounded_blood_elf(Creature creature) : base(creature) { }
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Scripts.Outlands
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
Player player = GetPlayerForEscort();
|
||||
if (!player)
|
||||
|
||||
@@ -289,13 +289,13 @@ namespace Scripts.Outlands
|
||||
{
|
||||
public npc_kayra_longmane() : base("npc_kayra_longmane") { }
|
||||
|
||||
class npc_kayra_longmaneAI : NpcEscortAI
|
||||
class npc_kayra_longmaneAI : EscortAI
|
||||
{
|
||||
public npc_kayra_longmaneAI(Creature creature) : base(creature) { }
|
||||
|
||||
public override void Reset() { }
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
Player player = GetPlayerForEscort();
|
||||
if (!player)
|
||||
|
||||
@@ -1163,7 +1163,7 @@ namespace Scripts.World.NpcSpecial
|
||||
}
|
||||
|
||||
[Script]
|
||||
class npc_garments_of_quests : NpcEscortAI
|
||||
class npc_garments_of_quests : EscortAI
|
||||
{
|
||||
public npc_garments_of_quests(Creature creature) : base(creature)
|
||||
{
|
||||
@@ -1254,7 +1254,7 @@ namespace Scripts.World.NpcSpecial
|
||||
}
|
||||
}
|
||||
|
||||
public override void WaypointReached(uint waypointId)
|
||||
public override void WaypointReached(uint waypointId, uint pathId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user