diff --git a/Source/Game/AI/CoreAI/UnitAI.cs b/Source/Game/AI/CoreAI/UnitAI.cs index 546f5078f..235d9dc65 100644 --- a/Source/Game/AI/CoreAI/UnitAI.cs +++ b/Source/Game/AI/CoreAI/UnitAI.cs @@ -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)); diff --git a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index c58835574..c99ff18c4 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -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,23 +75,14 @@ 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; - } + me.EngageWithTarget(who); + return true; } return false; @@ -99,28 +90,20 @@ namespace Game.AI public override void MoveInLineOfSight(Unit who) { - if (me.HasReactState(ReactStates.Aggressive) && !me.HasUnitState(UnitState.Stunned) && who.IsTargetableForAttack() && who.IsInAccessiblePlaceFor(me)) - { - if (HasEscortState(EscortState.Escorting) && AssistPlayerInCombatAgainst(who)) - return; + if (who == null) + return; - if (!me.CanFly() && me.GetDistanceZ(who) > SharedConst.CreatureAttackRangeZ) - return; + if (HasEscortState(EscortState.Escorting) && AssistPlayerInCombatAgainst(who)) + 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(); if (player) { @@ -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) - { - m_uiWPWaitTimer = 0; - - if (DespawnAtEnd) - { - Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints"); - - if (m_bCanReturnToStart) - { - float fRetX, fRetY, fRetZ; - me.GetRespawnPosition(out fRetX, out fRetY, out fRetZ); - - me.GetMotionMaster().MovePoint(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; - } - - if (m_bCanInstantRespawn && !WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc)) - { - me.SetDeathState(DeathState.JustDied); - me.Respawn(); - } - else - { - if (WorldConfig.GetBoolValue(WorldCfg.RespawnDynamicEscortNpc)) - me.GetMap().RemoveRespawnTime(SpawnObjectType.Creature, me.GetSpawnId(), true); - me.DespawnOrUnsummon(); - } - - return; - } - else - { - Log.outDebug(LogFilter.Scripts, "EscortAI reached end of waypoints with Despawn off"); - return; - } - - } - 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})."); + _pauseTimer = 0; - WaypointStart(currentWp.Id); + if (_ended) + { + _ended = false; + me.GetMotionMaster().MoveIdle(); - m_uiWPWaitTimer = 0; + if (_despawnAtEnd) + { + 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}"); + } + else if (_instantRespawn) + me.Respawn(); + else + 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 + _pauseTimer -= diff; } + //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); - RemoveEscortState(EscortState.Returning); - - if (m_uiWPWaitTimer == 0) - m_uiWPWaitTimer = 1; - } - else if (pointId == 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; - } - else - { - var currentWp = WaypointList[CurrentWPIndex]; - //Make sure that we are still on the right waypoint - if (currentWp.Id != pointId) + if (Id == EscortPointIds.LastPoint) { - Log.outError(LogFilter.Misc, $"TSCR ERROR: EscortAI reached waypoint out of order {pointId}, expected {currentWp.Id}, creature entry {me.GetEntry()}"); - return; + Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform has returned to original position before combat"); + + me.SetWalk(!_running); + RemoveEscortState(EscortState.Returning); + } + else if (Id == EscortPointIds.Home) + { + Log.outDebug(LogFilter.Scripts, "EscortAI.MovementInform: returned to home location and restarting waypoint path"); + _started = false; + } + } + else if (moveType == MovementGeneratorType.Waypoint) + { + 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 Waypoint {currentWp.Id} reached"); + Log.outDebug(LogFilter.Scripts, $"EscortAI.MovementInform: waypoint node {waypoint.id} reached"); - //Call WP function - WaypointReached(currentWp.Id); - - m_uiWPWaitTimer = currentWp.WaitTimeMs + 1; - - ++CurrentWPIndex; + // last point + if (Id == _path.nodes.Count - 1) + { + _started = false; + _ended = true; + _pauseTimer = 1000; + } } } - 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) - me.SetWalk(false); - else - Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set run mode, but is already running."); - } - else - { - if (m_bIsRunning) - me.SetWalk(true); - else - Log.outDebug(LogFilter.Scripts, "EscortAI attempt to set walk mode, but is already walking."); - } + if (on && !_running) + me.SetWalk(false); + else if (!on && _running) + me.SetWalk(true); - 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; + // set variables + _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().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); - } - - 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; - } + _resume = 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) @@ -577,72 +482,55 @@ namespace Game.AI return false; } - - public virtual void WaypointReached(uint pointId) { } - public virtual void WaypointStart(uint pointId) { } - public bool HasEscortState(EscortState escortState) { return m_uiEscortState.HasAnyFlag(escortState); } - public override bool IsEscorted() { return m_uiEscortState.HasAnyFlag(EscortState.Escorting); } + void SetPauseTimer(uint Timer) { _pauseTimer = Timer; } - public void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } - public float GetMaxPlayerDistance() { return MaxPlayerDistance; } + public bool HasEscortState(EscortState escortState) { return (_escortState & escortState) != 0; } + public override bool IsEscorted() { return _escortState.HasAnyFlag(EscortState.Escorting); } - 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; } + void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; } + float GetMaxPlayerDistance() { return _maxPlayerDistance; } - public Player GetPlayerForEscort() { return Global.ObjAccessor.GetPlayer(me, m_uiPlayerGUID); } + public void SetDespawnAtEnd(bool despawn) { _despawnAtEnd = despawn; } + public void SetDespawnAtFar(bool despawn) { _despawnAtFar = despawn; } - void AddEscortState(EscortState escortState) { m_uiEscortState |= escortState; } - void RemoveEscortState(EscortState escortState) { m_uiEscortState &= ~escortState; } + bool GetAttack() { return _activeAttacker; } // used in EnterEvadeMode override + void SetCanAttack(bool attack) { _activeAttacker = attack; } - ObjectGuid m_uiPlayerGUID; - uint m_uiWPWaitTimer; - uint m_uiPlayerCheckTimer; - EscortState m_uiEscortState; - float MaxPlayerDistance; + ObjectGuid GetEventStarterGUID() { return _playerGUID; } - Quest m_pQuestForEscort; //generally passed in Start() when regular escort script. + void AddEscortState(EscortState escortState) { _escortState |= escortState; } + void RemoveEscortState(EscortState escortState) { _escortState &= ~escortState; } - List WaypointList = new List(); - int CurrentWPIndex; + ObjectGuid _playerGUID; + uint _pauseTimer; + uint _playerCheckTimer; + EscortState _escortState; + float _maxPlayerDistance; - 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; + Quest _escortQuest; //generally passed in Start() when regular escort script. + + 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 diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index e2836b68a..65a3f837e 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -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) + _currentWaypointNode = nodeId; + _waypointPathEnded = false; + + _repeatWaypointPath = repeat; + + // Do not use AddEscortState, removing everything from previous + _escortState = SmartEscortState.Escorting; + + if (invoker && invoker.IsPlayer()) { - AddEscortState(SmartEscortState.Escorting); - mCanRepeatPath = repeat; - - SetRun(run); - - if (invoker != null && invoker.GetTypeId() == TypeId.Player) - { - mEscortNPCFlags = me.m_unitData.NpcFlags[0]; - me.SetNpcFlags(NPCFlags.None); - } - - mLastOOCPos = me.GetPosition(); - me.GetMotionMaster().MovePoint(wp.Id, wp.X, wp.Y, wp.Z); - GetScript().ProcessEventsFor(SmartEvents.WaypointStart, null, wp.Id, GetScript().GetPathId()); + _escortNPCFlags = me.m_unitData.NpcFlags[0]; + me.SetNpcFlags((NPCFlags)0); } + + 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 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 (_waypointPauseTimer < 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 - { - ResumePath(); - mWPReached = false; - mForcedPaused = false; - } - if (mLastWPIDReached == EventId.SmartEscortLastOCCPoint) - mWPReached = true; - } - mWPPauseTimer = 0; + if (!me.IsInCombat() && !HasEscortState(SmartEscortState.Returning)) + ResumePath(); } 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) - me.GetMotionMaster().MovePath(me.GetWaypointPath(), true); + 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) + public override void SpellHit(Unit caster, SpellInfo spellInfo) { - GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spell); + GetScript().ProcessEventsFor(SmartEvents.SpellHit, caster, 0, 0, false, spellInfo); } - public override void SpellHitTarget(Unit target, SpellInfo spell) + public override void SpellHitTarget(Unit target, SpellInfo spellInfo) { - GetScript().ProcessEventsFor(SmartEvents.SpellHitTarget, target, 0, 0, false, spell); + 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 (on && !me.HasReactState(ReactStates.Passive) && me.GetVictim() && me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Max) { - if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Idle) - { - 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(); + SetRun(mRun); + me.GetMotionMaster().MoveChase(me.GetVictim()); } + 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; - } - - mFollowArrivedTimer = 1000; - } - else - mFollowArrivedTimer -= diff; - } + if (!HasEscortState(SmartEscortState.Escorting))//dont mess up escort movement after combat + SetRun(mRun); + GetScript().OnReset(); } - 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 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 diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index 99aba6ac5..ee88b7d7d 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -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(3); float z = result.Read(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 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 GetPath(uint id) + public WaypointPath GetPath(uint id) { - return waypoint_map.LookupByKey(id); + return _waypointStore.LookupByKey(id); } public SmartScriptHolder FindLinkedSourceEvent(List list, uint eventId) @@ -1582,7 +1591,7 @@ namespace Game.AI } MultiMap[] mEventMap = new MultiMap[(int)SmartScriptType.Max]; - MultiMap waypoint_map = new MultiMap(); + Dictionary _waypointStore = new Dictionary(); Dictionary SmartAITypeMask = new Dictionary { @@ -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() { } diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 1e58628db..abed26187 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -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); } } } diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 0db83331d..f01b102ae 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -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) diff --git a/Source/Game/Entities/Creature/CreatureGroups.cs b/Source/Game/Entities/Creature/CreatureGroups.cs index 4f49dfa78..9273de037 100644 --- a/Source/Game/Entities/Creature/CreatureGroups.cs +++ b/Source/Game/Entities/Creature/CreatureGroups.cs @@ -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,11 +258,26 @@ 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 m_members = new Dictionary(); diff --git a/Source/Game/Entities/Unit/Unit.Movement.cs b/Source/Game/Entities/Unit/Unit.Movement.cs index a73c46f4f..cef2b634b 100644 --- a/Source/Game/Entities/Unit/Unit.Movement.cs +++ b/Source/Game/Entities/Unit/Unit.Movement.cs @@ -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)) diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs index ea2f3f545..cbd96619d 100644 --- a/Source/Game/Entities/Unit/Unit.Pets.cs +++ b/Source/Game/Entities/Unit/Unit.Pets.cs @@ -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(); diff --git a/Source/Game/Handlers/BattleGroundHandler.cs b/Source/Game/Handlers/BattleGroundHandler.cs index 6abc6036d..d2fea3cf1 100644 --- a/Source/Game/Handlers/BattleGroundHandler.cs +++ b/Source/Game/Handlers/BattleGroundHandler.cs @@ -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()); diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index c78ce4453..d3f4d8edb 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -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; } diff --git a/Source/Game/Handlers/NPCHandler.cs b/Source/Game/Handlers/NPCHandler.cs index 2a83eab59..08909c245 100644 --- a/Source/Game/Handlers/NPCHandler.cs +++ b/Source/Game/Handlers/NPCHandler.cs @@ -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; diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index a8a3904fa..b7a2ab017 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -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)) diff --git a/Source/Game/Handlers/TaxiHandler.cs b/Source/Game/Handlers/TaxiHandler.cs index e0145ffa4..6d3e483ab 100644 --- a/Source/Game/Handlers/TaxiHandler.cs +++ b/Source/Game/Handlers/TaxiHandler.cs @@ -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); diff --git a/Source/Game/Movement/Generators/FormationMovement.cs b/Source/Game/Movement/Generators/FormationMovement.cs index 1fc6add10..06104429b 100644 --- a/Source/Game/Movement/Generators/FormationMovement.cs +++ b/Source/Game/Movement/Generators/FormationMovement.cs @@ -22,7 +22,7 @@ namespace Game.Movement { public class FormationMovementGenerator : MovementGeneratorMedium { - 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; diff --git a/Source/Game/Movement/Generators/MovementGenerators.cs b/Source/Game/Movement/Generators/MovementGenerators.cs index a241d6356..fde14882e 100644 --- a/Source/Game/Movement/Generators/MovementGenerators.cs +++ b/Source/Game/Movement/Generators/MovementGenerators.cs @@ -34,7 +34,11 @@ namespace Game.Movement public abstract MovementGeneratorType GetMovementGeneratorType(); 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) { diff --git a/Source/Game/Movement/Generators/PointMovement.cs b/Source/Game/Movement/Generators/PointMovement.cs index 19f74e8b6..fa762cf13 100644 --- a/Source/Game/Movement/Generators/PointMovement.cs +++ b/Source/Game/Movement/Generators/PointMovement.cs @@ -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(); diff --git a/Source/Game/Movement/Generators/RandomMovement.cs b/Source/Game/Movement/Generators/RandomMovement.cs index 7f4e88a99..987012e0e 100644 --- a/Source/Game/Movement/Generators/RandomMovement.cs +++ b/Source/Game/Movement/Generators/RandomMovement.cs @@ -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() diff --git a/Source/Game/Movement/Generators/WaypointMovement.cs b/Source/Game/Movement/Generators/WaypointMovement.cs index 7816d6cfa..e25ab42a2 100644 --- a/Source/Game/Movement/Generators/WaypointMovement.cs +++ b/Source/Game/Movement/Generators/WaypointMovement.cs @@ -28,21 +28,45 @@ namespace Game.Movement { public class WaypointMovementGenerator : MovementGeneratorMedium { - 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); - 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; + 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); + } - 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); - } - void OnArrived(Creature creature) - { - if (path == null || path.Empty()) - return; - - if (isArrivalDone) - return; - - isArrivalDone = true; - - var current = path.LookupByIndex((int)currentNode); - - if (current.eventId != 0 && RandomHelper.URand(0, 99) < current.eventChance) + if (!_nextMoveTime.Passed()) { - 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); + _nextMoveTime.Update((int)diff); + if (_nextMoveTime.Passed()) + return StartMoveNow(creature); + } + else + { + // Set home position at place on waypoint movement. + if (creature.GetTransGUID().IsEmpty()) + creature.SetHomePosition(creature.GetPosition()); + + if (creature.MoveSpline.Finalized()) + { + OnArrived(creature); + _isArrivalDone = true; + + if (_nextMoveTime.Passed()) + return StartMove(creature); + } + else if (_recalculateSpeed) + { + if (_nextMoveTime.Passed()) + StartMove(creature); + } } - // Inform script - MovementInform(creature); - creature.UpdateWaypointID(currentNode); - - if (current.delay != 0) - { - creature.ClearUnitState(UnitState.RoamingMove); - Stop((int)current.delay); - } + return true; } - public override MovementGeneratorType GetMovementGeneratorType() + void MovementInform(Creature creature) { - return MovementGeneratorType.Waypoint; + if (creature.IsAIEnabled) + creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); } - public uint GetCurrentNode() { return currentNode; } + 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; - TimeTrackerSmall nextMoveTime; + WaypointNode waypoint = _path.nodes.ElementAt(_currentNode); - bool isArrivalDone; - uint pathId; - bool repeating; - List path; - uint 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 { + 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 GetPath() { return i_path; } + public List 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 i_path = new List(); + List _path = new List(); + int _currentNode; List _pointsForPathSwitch = new List(); //! node indexes and costs where TaxiPath changes class TaxiNodeChangeInfo diff --git a/Source/Game/Movement/MotionMaster.cs b/Source/Game/Movement/MotionMaster.cs index 7f984a4ec..724bcee28 100644 --- a/Source/Game/Movement/MotionMaster.cs +++ b/Source/Game/Movement/MotionMaster.cs @@ -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); diff --git a/Source/Game/Movement/WaypointManager.cs b/Source/Game/Movement/WaypointManager.cs index c98796cb8..37ea1ce76 100644 --- a/Source/Game/Movement/WaypointManager.cs +++ b/Source/Game/Movement/WaypointManager.cs @@ -23,10 +23,7 @@ namespace Game { public sealed class WaypointManager : Singleton { - WaypointManager() - { - _waypointStore = new MultiMap(); - } + 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(1); - wp.x = x; - wp.y = y; - wp.z = z; - wp.orientation = o; - wp.moveType = (WaypointMoveType)result.Read(6); + WaypointNode waypoint = new WaypointNode(); + waypoint.id = result.Read(1); + waypoint.x = x; + waypoint.y = y; + waypoint.z = z; + waypoint.orientation = o; + waypoint.moveType = (WaypointMoveType)result.Read(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(7); - wp.eventId = result.Read(8); - wp.eventChance = result.Read(9); + waypoint.delay = result.Read(7); + waypoint.eventId = result.Read(8); + waypoint.eventChance = result.Read(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 values = new List(); do { float x = result.Read(1); @@ -101,42 +105,43 @@ namespace Game GridDefines.NormalizeMapCoord(ref x); GridDefines.NormalizeMapCoord(ref y); - WaypointData wp = new WaypointData(); - wp.id = result.Read(0); - wp.x = x; - wp.y = y; - wp.z = z; - wp.orientation = o; - wp.moveType = (WaypointMoveType)result.Read(5); + WaypointNode waypoint = new WaypointNode(); + waypoint.id = result.Read(0); + waypoint.x = x; + waypoint.y = y; + waypoint.z = z; + waypoint.orientation = o; + waypoint.moveType = (WaypointMoveType)result.Read(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(6); - wp.eventId = result.Read(7); - wp.eventChance = result.Read(8); - - _waypointStore.Add(id, wp); + waypoint.delay = result.Read(6); + waypoint.eventId = result.Read(7); + waypoint.eventChance = result.Read(8); + values.Add(waypoint); } while (result.NextRow()); + + _waypointStore[id] = new WaypointPath(id, values); } - public List GetPath(uint id) + public WaypointPath GetPath(uint id) { return _waypointStore.LookupByKey(id); } - MultiMap _waypointStore; + Dictionary _waypointStore = new Dictionary(); } - 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 _nodes) + { + id = _id; + nodes = _nodes; + } + + public List nodes = new List(); + public uint id; + } + public enum WaypointMoveType { Walk, diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 50107fb8d..bbca2ea1d 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -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(0); + entryCount = result.Read(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(0); + uint id = result.Read(1); + float x = result.Read(2); + float y = result.Read(3); + float z = result.Read(4); + uint waitTime = result.Read(5); - temp.uiCreatureEntry = result.Read(0); - uint uiEntry = temp.uiCreatureEntry; - temp.uiPointId = result.Read(1); - temp.fX = result.Read(2); - temp.fY = result.Read(3); - temp.fZ = result.Read(4); - temp.uiWaitTime = result.Read(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 GetSplineChain(Creature who, ushort chainId) { return GetSplineChain(who.GetEntry(), chainId); } + List GetSplineChain(uint entry, ushort chainId) { return m_mSplineChainsMap.LookupByKey(Tuple.Create(entry, chainId)); @@ -1242,11 +1252,6 @@ namespace Game.Scripting GetScriptRegistry().AddScript(script); } - public List GetPointMoveList(uint creatureEntry) - { - return m_mPointMoveMap.LookupByKey(creatureEntry); - } - ScriptRegistry GetScriptRegistry() where T : ScriptObject { if (ScriptStorage.ContainsKey(typeof(T))) @@ -1259,7 +1264,7 @@ namespace Game.Scripting public Dictionary spellSummaryStorage = new Dictionary(); Hashtable ScriptStorage = new Hashtable(); - MultiMap m_mPointMoveMap = new MultiMap(); + Dictionary _waypointStore = new Dictionary(); // creature entry + chain ID MultiMap, SplineChainLink> m_mSplineChainsMap = new MultiMap, SplineChainLink>(); // spline chains @@ -1348,16 +1353,6 @@ namespace Game.Scripting Dictionary ScriptMap = new Dictionary(); } - 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 diff --git a/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs b/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs index c76909db8..71d43c6a7 100644 --- a/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs +++ b/Source/Scripts/EasternKingdoms/Karazhan/OperaEvent.cs @@ -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) { diff --git a/Source/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs b/Source/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs index afec798fa..8be52f432 100644 --- a/Source/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs +++ b/Source/Scripts/EasternKingdoms/ScarletEnclave/Chapter1.cs @@ -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) { diff --git a/Source/Scripts/Kalimdor/ZoneAshenvale.cs b/Source/Scripts/Kalimdor/ZoneAshenvale.cs index bcd58d65a..ffb72ae24 100644 --- a/Source/Scripts/Kalimdor/ZoneAshenvale.cs +++ b/Source/Scripts/Kalimdor/ZoneAshenvale.cs @@ -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) diff --git a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs index 5c71ca8f2..776b2889b 100644 --- a/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs +++ b/Source/Scripts/Northrend/CrusadersColiseum/TrialOfTheChampion/GrandChampions.cs @@ -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) { diff --git a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs index 0f21a82ba..ae1b9dee4 100644 --- a/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs +++ b/Source/Scripts/Northrend/IcecrownCitadel/IcecrownCitadel.cs @@ -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) diff --git a/Source/Scripts/Northrend/Ulduar/FlameLeviathan.cs b/Source/Scripts/Northrend/Ulduar/FlameLeviathan.cs index 4513df335..f67700f4f 100644 --- a/Source/Scripts/Northrend/Ulduar/FlameLeviathan.cs +++ b/Source/Scripts/Northrend/Ulduar/FlameLeviathan.cs @@ -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() { diff --git a/Source/Scripts/Outlands/HellfirePeninsula.cs b/Source/Scripts/Outlands/HellfirePeninsula.cs index aa79cbef5..af4124259 100644 --- a/Source/Scripts/Outlands/HellfirePeninsula.cs +++ b/Source/Scripts/Outlands/HellfirePeninsula.cs @@ -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) diff --git a/Source/Scripts/Outlands/Zangarmarsh.cs b/Source/Scripts/Outlands/Zangarmarsh.cs index 022044bdf..f02684eb3 100644 --- a/Source/Scripts/Outlands/Zangarmarsh.cs +++ b/Source/Scripts/Outlands/Zangarmarsh.cs @@ -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) diff --git a/Source/Scripts/World/NpcSpecial.cs b/Source/Scripts/World/NpcSpecial.cs index 005898afe..874b9ebf2 100644 --- a/Source/Scripts/World/NpcSpecial.cs +++ b/Source/Scripts/World/NpcSpecial.cs @@ -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) { }