diff --git a/Source/Framework/Constants/Movement/MovementConst.cs b/Source/Framework/Constants/Movement/MovementConst.cs index c8c7a3da8..061757860 100644 --- a/Source/Framework/Constants/Movement/MovementConst.cs +++ b/Source/Framework/Constants/Movement/MovementConst.cs @@ -17,11 +17,23 @@ namespace Framework.Constants { + public enum MovementGeneratorMode + { + Default = 0, + Override + } + + public enum MovementGeneratorPriority + { + None = 0, + Normal, + Highest + } + public enum MovementSlot { - Idle, + Default = 0, Active, - Controlled, Max } @@ -50,6 +62,41 @@ namespace Framework.Constants Max } + public enum MotionMasterFlags + { + None = 0x0, + Update = 0x1, // Update in progress + StaticInitializationPending = 0x2 + } + + public enum MotionMasterDelayedActionType + { + Clear = 0, + ClearSlot, + ClearMode, + ClearPriority, + Add, + Remove, + RemoveType, + Initialize + } + + public enum MovementGeneratorFlags + { + None = 0x000, + InitializationPending = 0x001, + Initialized = 0x002, + SpeedUpdatePending = 0x004, + Interrupted = 0x008, + Paused = 0x010, + TimedPaused = 0x020, + Deactivated = 0x040, + InformEnabled = 0x080, + Finalized = 0x100, + + Transitory = SpeedUpdatePending | Interrupted + } + public struct EventId { public const uint Charge = 1003; @@ -63,6 +110,7 @@ namespace Framework.Constants public const uint Face = 1006; public const uint VehicleBoard = 1007; public const uint VehicleExit = 1008; + public const uint AssistMove = 1009; public const uint SmartRandomPoint = 0xFFFFFE; public const uint SmartEscortLastOCCPoint = 0xFFFFFF; diff --git a/Source/Framework/Constants/SmartAIConst.cs b/Source/Framework/Constants/SmartAIConst.cs index 251fa77ee..c227cd811 100644 --- a/Source/Framework/Constants/SmartAIConst.cs +++ b/Source/Framework/Constants/SmartAIConst.cs @@ -381,7 +381,7 @@ namespace Framework.Constants LoadEquipment = 124, // id TriggerRandomTimedEvent = 125, // id min range, id max range RemoveAllGameobjects = 126, - StopMotion = 127, // stopMoving, movementExpired + RemoveMovement = 127, // movementType, forced PlayAnimkit = 128, ScenePlay = 129, // sceneId SceneCancel = 130, // sceneId diff --git a/Source/Game/AI/AISelector.cs b/Source/Game/AI/AISelector.cs index f30b2a10c..8ca1192a7 100644 --- a/Source/Game/AI/AISelector.cs +++ b/Source/Game/AI/AISelector.cs @@ -97,7 +97,7 @@ namespace Game.AI return new NullCreatureAI(creature); } - public static IMovementGenerator SelectMovementAI(Unit unit) + public static MovementGenerator SelectMovementGenerator(Unit unit) { MovementGeneratorType type = unit.GetDefaultMovementType(); Creature creature = unit.ToCreature(); diff --git a/Source/Game/AI/CoreAI/CreatureAI.cs b/Source/Game/AI/CoreAI/CreatureAI.cs index 3e5e86ba2..c7eb741c3 100644 --- a/Source/Game/AI/CoreAI/CreatureAI.cs +++ b/Source/Game/AI/CoreAI/CreatureAI.cs @@ -156,9 +156,7 @@ namespace Game.AI me.SendAIReaction(AiReaction.Alert); // Face the unit (stealthed player) and set distracted state for 5 seconds - me.GetMotionMaster().MoveDistract(5 * Time.InMilliseconds); - me.StopMoving(); - me.SetFacingTo(me.GetAbsoluteAngle(who)); + me.GetMotionMaster().MoveDistract(5 * Time.InMilliseconds, me.GetAbsoluteAngle(who)); } // Called for reaction at stopping attack at no attackers or targets @@ -174,8 +172,8 @@ namespace Game.AI Unit owner = me.GetCharmerOrOwner(); if (owner != null) { - me.GetMotionMaster().Clear(false); - me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle()); } else { diff --git a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index 4c8cdc810..6c8fc48ae 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -44,21 +44,6 @@ namespace Game.AI return Global.ObjAccessor.GetPlayer(me, _playerGUID); } - public override void AttackStart(Unit target) - { - if (target == null) - return; - - if (me.Attack(target, true)) - { - if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) - me.GetMotionMaster().MovementExpired(); - - if (IsCombatMovementAllowed()) - me.GetMotionMaster().MoveChase(target); - } - } - //see followerAI bool AssistPlayerInCombatAgainst(Unit who) { @@ -236,7 +221,7 @@ namespace Game.AI else if (_resume) { _resume = false; - IMovementGenerator movementGenerator = me.GetMotionMaster().GetMotionSlot(MovementSlot.Idle); + MovementGenerator movementGenerator = me.GetMotionMaster().GetCurrentMovementGenerator(MovementSlot.Default); if (movementGenerator != null) movementGenerator.Resume(0); } @@ -436,7 +421,7 @@ namespace Game.AI 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."); me.GetMotionMaster().MoveIdle(); - me.GetMotionMaster().Clear(MovementSlot.Active); + me.GetMotionMaster().Clear(MovementGeneratorPriority.Normal); //disable npcflags me.SetNpcFlags(NPCFlags.None); @@ -464,7 +449,7 @@ namespace Game.AI if (on) { AddEscortState(EscortState.Paused); - IMovementGenerator movementGenerator = me.GetMotionMaster().GetMotionSlot(MovementSlot.Idle); + MovementGenerator movementGenerator = me.GetMotionMaster().GetCurrentMovementGenerator(MovementSlot.Default); if (movementGenerator != null) movementGenerator.Pause(0); } diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index 3fd037d94..db3264885 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -158,7 +158,7 @@ namespace Game.AI { if (!HasEscortState(SmartEscortState.Escorting)) { - me.PauseMovement(delay, MovementSlot.Idle, forced); + me.PauseMovement(delay, MovementSlot.Default, forced); if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint) { var (nodeId, pathId) = me.GetCurrentWaypointInfo(); @@ -597,8 +597,11 @@ namespace Game.AI CreatureGroup formation = me.GetFormation(); if (formation == null || formation.GetLeader() == me || !formation.IsFormed()) { - if (me.GetMotionMaster().GetMotionSlotType(MovementSlot.Idle) != MovementGeneratorType.Waypoint && me.GetWaypointPath() != 0) - me.GetMotionMaster().MovePath(me.GetWaypointPath(), true); + if (me.GetMotionMaster().GetCurrentMovementGeneratorType(MovementSlot.Default) != MovementGeneratorType.Waypoint) + { + if (me.GetWaypointPath() != 0) + me.GetMotionMaster().MovePath(me.GetWaypointPath(), true); + } else me.ResumeMovement(); } @@ -644,7 +647,8 @@ namespace Game.AI if (who != null && me.Attack(who, _canAutoAttack)) { - me.GetMotionMaster().Clear(MovementSlot.Active); + me.GetMotionMaster().Clear(MovementGeneratorPriority.Normal); + me.PauseMovement(); if (_canCombatMove) { @@ -826,13 +830,20 @@ namespace Game.AI if (me.IsEngaged()) { - if (on && !me.HasReactState(ReactStates.Passive) && me.GetVictim() && me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Max) + if (on) { - SetRun(_run); - me.GetMotionMaster().MoveChase(me.GetVictim()); + if (!me.HasReactState(ReactStates.Passive) && me.GetVictim() && !me.GetMotionMaster().HasMovementGenerator(movement => movement.Mode == MovementGeneratorMode.Default && movement.Priority == MovementGeneratorPriority.Normal)) + { + SetRun(_run); + me.GetMotionMaster().MoveChase(me.GetVictim()); + } + } + else + { + var movement = me.GetMotionMaster().GetMovementGenerator(a => a.GetMovementGeneratorType() == MovementGeneratorType.Chase && a.Mode == MovementGeneratorMode.Default && a.Priority == MovementGeneratorPriority.Normal); + if (movement != null) + me.GetMotionMaster().Remove(movement); } - else if (!on && me.GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Chase) - me.GetMotionMaster().Clear(MovementSlot.Active); } } diff --git a/Source/Game/AI/SmartScripts/SmartAIManager.cs b/Source/Game/AI/SmartScripts/SmartAIManager.cs index f7268ad21..ea1a7611c 100644 --- a/Source/Game/AI/SmartScripts/SmartAIManager.cs +++ b/Source/Game/AI/SmartScripts/SmartAIManager.cs @@ -1511,7 +1511,7 @@ namespace Game.AI case SmartActions.RemoveAllGameobjects: case SmartActions.SpawnSpawngroup: case SmartActions.DespawnSpawngroup: - case SmartActions.StopMotion: + case SmartActions.RemoveMovement: break; default: Log.outError(LogFilter.ScriptsAi, "SmartAIMgr: Not handled action_type({0}), event_type({1}), Entry {2} SourceType {3} Event {4}, skipped.", e.GetActionType(), e.GetEventType(), e.EntryOrGuid, e.GetScriptType(), e.EventId); @@ -2515,7 +2515,7 @@ namespace Game.AI public RandomTimedEvent randomTimedEvent; [FieldOffset(4)] - public StopMotion stopMotion; + public RemoveMovement removeMovement; [FieldOffset(4)] public RespawnData respawnData; @@ -3022,10 +3022,10 @@ namespace Game.AI public uint minId; public uint maxId; } - public struct StopMotion + public struct RemoveMovement { - public uint stopMovement; - public uint movementExpired; + public uint movementType; + public uint forced; } public struct RespawnData { diff --git a/Source/Game/AI/SmartScripts/SmartScript.cs b/Source/Game/AI/SmartScripts/SmartScript.cs index 5b7b58b7e..e7d98a718 100644 --- a/Source/Game/AI/SmartScripts/SmartScript.cs +++ b/Source/Game/AI/SmartScripts/SmartScript.cs @@ -2293,16 +2293,16 @@ namespace Game.AI break; } - case SmartActions.StopMotion: + case SmartActions.RemoveMovement: { foreach (var target in targets) { if (IsUnit(target)) { - if (e.Action.stopMotion.stopMovement != 0) + if (e.Action.removeMovement.movementType != 0 && e.Action.removeMovement.movementType < (uint)MovementGeneratorType.Max) + target.ToUnit().GetMotionMaster().Remove((MovementGeneratorType)e.Action.removeMovement.movementType); + if (e.Action.removeMovement.forced != 0) target.ToUnit().StopMoving(); - if (e.Action.stopMotion.movementExpired != 0) - target.ToUnit().GetMotionMaster().MovementExpired(); } } diff --git a/Source/Game/Chat/Commands/GoCommands.cs b/Source/Game/Chat/Commands/GoCommands.cs index e295cf871..63888da70 100644 --- a/Source/Game/Chat/Commands/GoCommands.cs +++ b/Source/Game/Chat/Commands/GoCommands.cs @@ -97,13 +97,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(mapId, x, y, z, o); @@ -137,13 +133,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(gy.Loc); return true; @@ -179,13 +171,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case Map map = Global.MapMgr.CreateBaseMap(mapId); float z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); @@ -227,13 +215,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(goData.spawnPoint); return true; @@ -287,13 +271,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case Map map = Global.MapMgr.CreateBaseMap(mapId); z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); @@ -333,13 +313,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(node.ContinentID, node.Pos.X, node.Pos.Y, node.Pos.Z, player.GetOrientation()); return true; @@ -372,13 +348,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(at.ContinentID, at.Pos.X, at.Pos.Y, at.Pos.Z, player.GetOrientation()); return true; @@ -439,13 +411,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case float z = Math.Max(map.GetStaticHeight(PhasingHandler.EmptyPhaseShift, x, y, MapConst.MaxHeight), map.GetWaterLevel(PhasingHandler.EmptyPhaseShift, x, y)); @@ -501,13 +469,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(mapId, x, y, z, ort); return true; @@ -548,13 +512,12 @@ namespace Game.Chat.Commands } Player player = handler.GetSession().GetPlayer(); + + // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case ticket.TeleportTo(player); return true; @@ -592,13 +555,9 @@ namespace Game.Chat.Commands // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(player.GetMapId(), x, y, z, o); return true; diff --git a/Source/Game/Chat/Commands/GroupCommands.cs b/Source/Game/Chat/Commands/GroupCommands.cs index c14f92592..a514bab05 100644 --- a/Source/Game/Chat/Commands/GroupCommands.cs +++ b/Source/Game/Chat/Commands/GroupCommands.cs @@ -109,13 +109,9 @@ namespace Game.Chat // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case // before GM float x, y, z; diff --git a/Source/Game/Chat/Commands/MiscCommands.cs b/Source/Game/Chat/Commands/MiscCommands.cs index f0f61cdee..ce36536e4 100644 --- a/Source/Game/Chat/Commands/MiscCommands.cs +++ b/Source/Game/Chat/Commands/MiscCommands.cs @@ -354,13 +354,9 @@ namespace Game.Chat // stop flight if need if (_player.IsInFlight()) - { - _player.GetMotionMaster().MovementExpired(); - _player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + _player.FinishTaxiFlight(); else - _player.SaveRecallPosition(); + _player.SaveRecallPosition(); // save only in non-flight case // to point to see at target with same orientation float x, y, z; @@ -387,13 +383,9 @@ namespace Game.Chat // stop flight if need if (_player.IsInFlight()) - { - _player.GetMotionMaster().MovementExpired(); - _player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + _player.FinishTaxiFlight(); else - _player.SaveRecallPosition(); + _player.SaveRecallPosition(); // save only in non-flight case loc.SetOrientation(_player.GetOrientation()); _player.TeleportTo(loc); @@ -1216,20 +1208,19 @@ namespace Game.Chat handler.SendSysMessage(CypherStrings.MovegensList, (unit.IsTypeId(TypeId.Player) ? "Player" : "Creature"), unit.GetGUID().ToString()); - MotionMaster motionMaster = unit.GetMotionMaster(); - float x, y, z; - motionMaster.GetDestination(out x, out y, out z); - - for (byte i = 0; i < (int)MovementSlot.Max; ++i) + if (unit.GetMotionMaster().Empty()) { - IMovementGenerator movementGenerator = motionMaster.GetMotionSlot(i); - if (movementGenerator == null) - { - handler.SendSysMessage("Empty"); - continue; - } + handler.SendSysMessage("Empty"); + return true; + } - switch (movementGenerator.GetMovementGeneratorType()) + float x, y, z; + unit.GetMotionMaster().GetDestination(out x, out y, out z); + + var list = unit.GetMotionMaster().GetMovementGeneratorsInformation(); + foreach (MovementGeneratorInformation info in list) + { + switch (info.Type) { case MovementGeneratorType.Idle: handler.SendSysMessage(CypherStrings.MovegensIdle); @@ -1244,45 +1235,33 @@ namespace Game.Chat handler.SendSysMessage(CypherStrings.MovegensConfused); break; case MovementGeneratorType.Chase: - { - Unit target = ((ChaseMovementGenerator)movementGenerator).GetTarget(); - - if (!target) - handler.SendSysMessage(CypherStrings.MovegensChaseNull); - else if (target.IsTypeId(TypeId.Player)) - handler.SendSysMessage(CypherStrings.MovegensChasePlayer, target.GetName(), target.GetGUID().ToString()); - else - handler.SendSysMessage(CypherStrings.MovegensChaseCreature, target.GetName(), target.GetGUID().ToString()); - break; - } + if (info.TargetGUID.IsEmpty()) + handler.SendSysMessage(CypherStrings.MovegensChaseNull); + else if (info.TargetGUID.IsPlayer()) + handler.SendSysMessage(CypherStrings.MovegensChasePlayer, info.TargetName, info.TargetGUID.ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensChaseCreature, info.TargetName, info.TargetGUID.ToString()); + break; case MovementGeneratorType.Follow: - { - Unit target = ((FollowMovementGenerator)movementGenerator).GetTarget(); - - if (!target) - handler.SendSysMessage(CypherStrings.MovegensFollowNull); - else if (target.IsTypeId(TypeId.Player)) - handler.SendSysMessage(CypherStrings.MovegensFollowPlayer, target.GetName(), target.GetGUID().ToString()); - else - handler.SendSysMessage(CypherStrings.MovegensFollowCreature, target.GetName(), target.GetGUID().ToString()); - break; - } + if (info.TargetGUID.IsEmpty()) + handler.SendSysMessage(CypherStrings.MovegensFollowNull); + else if (info.TargetGUID.IsPlayer()) + handler.SendSysMessage(CypherStrings.MovegensFollowPlayer, info.TargetName, info.TargetGUID.ToString()); + else + handler.SendSysMessage(CypherStrings.MovegensFollowCreature, info.TargetName, info.TargetGUID.ToString()); + break; case MovementGeneratorType.Home: - { - if (unit.IsTypeId(TypeId.Unit)) - handler.SendSysMessage(CypherStrings.MovegensHomeCreature, x, y, z); - else - handler.SendSysMessage(CypherStrings.MovegensHomePlayer); - break; - } + if (unit.IsTypeId(TypeId.Unit)) + handler.SendSysMessage(CypherStrings.MovegensHomeCreature, x, y, z); + else + handler.SendSysMessage(CypherStrings.MovegensHomePlayer); + break; case MovementGeneratorType.Flight: handler.SendSysMessage(CypherStrings.MovegensFlight); break; case MovementGeneratorType.Point: - { - handler.SendSysMessage(CypherStrings.MovegensPoint, x, y, z); - break; - } + handler.SendSysMessage(CypherStrings.MovegensPoint, x, y, z); + break; case MovementGeneratorType.Fleeing: handler.SendSysMessage(CypherStrings.MovegensFear); break; @@ -1293,7 +1272,7 @@ namespace Game.Chat handler.SendSysMessage(CypherStrings.MovegensEffect); break; default: - handler.SendSysMessage(CypherStrings.MovegensUnknown, movementGenerator.GetMovementGeneratorType()); + handler.SendSysMessage(CypherStrings.MovegensUnknown, info.Type); break; } } @@ -1915,11 +1894,7 @@ namespace Game.Chat } // stop flight if need - if (target.IsInFlight()) - { - target.GetMotionMaster().MovementExpired(); - target.CleanupAfterTaxiFlight(); - } + target.FinishTaxiFlight(); target.Recall(); return true; @@ -2162,14 +2137,10 @@ namespace Game.Chat target.SendSysMessage(CypherStrings.SummonedBy, handler.PlayerLink(_player.GetName())); // stop flight if need - if (target.IsInFlight()) - { - target.GetMotionMaster().MovementExpired(); - target.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + if (_player.IsInFlight()) + _player.FinishTaxiFlight(); else - target.SaveRecallPosition(); + _player.SaveRecallPosition(); // save only in non-flight case // before GM float x, y, z; diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index 61092b292..22c2cbc6e 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -976,23 +976,23 @@ namespace Game.Chat return false; } - if (/*creature.GetMotionMaster().empty() ||*/ - creature.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Follow) + MovementGenerator movement = creature.GetMotionMaster().GetMovementGenerator(a => + { + if (a.GetMovementGeneratorType() == MovementGeneratorType.Follow) + { + FollowMovementGenerator followMovement = a as FollowMovementGenerator; + return followMovement != null && followMovement.GetTarget() == player; + } + return false; + }); + + if (movement != null) { handler.SendSysMessage(CypherStrings.CreatureNotFollowYou, creature.GetName()); return false; } - FollowMovementGenerator mgen = (FollowMovementGenerator)creature.GetMotionMaster().Top(); - if (mgen.GetTarget() != player) - { - handler.SendSysMessage(CypherStrings.CreatureNotFollowYou, creature.GetName()); - return false; - } - - // reset movement - creature.GetMotionMaster().MovementExpired(true); - + creature.GetMotionMaster().Remove(movement); handler.SendSysMessage(CypherStrings.CreatureNotFollowYouNow, creature.GetName()); return true; } diff --git a/Source/Game/Chat/Commands/TeleCommands.cs b/Source/Game/Chat/Commands/TeleCommands.cs index 08879479c..9f8789900 100644 --- a/Source/Game/Chat/Commands/TeleCommands.cs +++ b/Source/Game/Chat/Commands/TeleCommands.cs @@ -35,40 +35,35 @@ namespace Game.Chat if (args.Empty()) return false; - Player me = handler.GetPlayer(); + Player player = handler.GetPlayer(); GameTele tele = handler.ExtractGameTeleFromLink(args); - if (tele == null) { handler.SendSysMessage(CypherStrings.CommandTeleNotfound); return false; } - if (me.IsInCombat() && !handler.GetSession().HasPermission(RBACPermissions.CommandTeleName)) + if (player.IsInCombat() && !handler.GetSession().HasPermission(RBACPermissions.CommandTeleName)) { handler.SendSysMessage(CypherStrings.YouInCombat); return false; } var map = CliDB.MapStorage.LookupByKey(tele.mapId); - if (map == null || (map.IsBattlegroundOrArena() && (me.GetMapId() != tele.mapId || !me.IsGameMaster()))) + if (map == null || (map.IsBattlegroundOrArena() && (player.GetMapId() != tele.mapId || !player.IsGameMaster()))) { handler.SendSysMessage(CypherStrings.CannotTeleToBg); return false; } // stop flight if need - if (me.IsInFlight()) - { - me.GetMotionMaster().MovementExpired(); - me.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + if (player.IsInFlight()) + player.FinishTaxiFlight(); else - me.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case - me.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); + player.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); return true; } @@ -196,13 +191,9 @@ namespace Game.Chat // stop flight if need if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + player.FinishTaxiFlight(); else - player.SaveRecallPosition(); + player.SaveRecallPosition(); // save only in non-flight case player.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); } @@ -270,13 +261,9 @@ namespace Game.Chat // stop flight if need if (target.IsInFlight()) - { - target.GetMotionMaster().MovementExpired(); - target.CleanupAfterTaxiFlight(); - } - // save only in non-flight case + target.FinishTaxiFlight(); else - target.SaveRecallPosition(); + target.SaveRecallPosition(); // save only in non-flight case target.TeleportTo(tele.mapId, tele.posX, tele.posY, tele.posZ, tele.orientation); } diff --git a/Source/Game/DungeonFinding/LFGManager.cs b/Source/Game/DungeonFinding/LFGManager.cs index a0c5f8de1..53a7bcf5b 100644 --- a/Source/Game/DungeonFinding/LFGManager.cs +++ b/Source/Game/DungeonFinding/LFGManager.cs @@ -1264,11 +1264,7 @@ namespace Game.DungeonFinding if (!player.GetMap().IsDungeon()) player.SetBattlegroundEntryPoint(); - if (player.IsInFlight()) - { - player.GetMotionMaster().MovementExpired(); - player.CleanupAfterTaxiFlight(); - } + player.FinishTaxiFlight(); if (!player.TeleportTo(mapid, x, y, z, orientation)) error = LfgTeleportResult.NoReturnLocation; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 4e1e09cb9..21db49347 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -110,7 +110,7 @@ namespace Game.Entities public bool IsReturningHome() { - if (GetMotionMaster().GetMotionSlotType(MovementSlot.Active) == MovementGeneratorType.Home) + if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Home) return true; return false; @@ -551,8 +551,7 @@ namespace Game.Entities { Unit charmer = Global.ObjAccessor.GetUnit(this, LastCharmerGUID); if (charmer) - if (CanStartAttack(charmer, true)) - i_AI.AttackStart(charmer); + EngageWithTarget(charmer); } LastCharmerGUID.Clear(); @@ -1917,7 +1916,8 @@ namespace Game.Entities SetNativeDisplayId(display.CreatureDisplayID, display.DisplayScale); } - GetMotionMaster().InitDefault(); + GetMotionMaster().InitializeDefault(); + //Re-initialize reactstate that could be altered by movementgenerators InitializeReactState(); diff --git a/Source/Game/Entities/Creature/CreatureGroups.cs b/Source/Game/Entities/Creature/CreatureGroups.cs index b3a5ae719..41914f702 100644 --- a/Source/Game/Entities/Creature/CreatureGroups.cs +++ b/Source/Game/Entities/Creature/CreatureGroups.cs @@ -266,7 +266,8 @@ namespace Game.Entities creature.GetMotionMaster().Initialize(); else creature.GetMotionMaster().MoveIdle(); - Log.outDebug(LogFilter.Unit, "Set {0} movement for member GUID: {1}", dismiss ? "default" : "idle", creature.GetGUID().ToString()); + + Log.outDebug(LogFilter.Unit, $"CreatureGroup::FormationReset: Set {(dismiss ? "default" : "idle")} movement for member {creature.GetGUID()}"); } } @@ -314,20 +315,20 @@ namespace Game.Entities if (!member.IsFlying()) member.UpdateGroundPositionZ(dx, dy, ref dz); - Position point = new(dx, dy, dz, destination.GetOrientation()); - - member.GetMotionMaster().MoveFormation(id, point, moveType, !member.IsWithinDist(_leader, dist + 5.0f), orientation); member.SetHomePosition(dx, dy, dz, pathangle); + + Position point = new(dx, dy, dz, destination.GetOrientation()); + member.GetMotionMaster().MoveFormation(id, point, moveType, !member.IsWithinDist(_leader, dist + 5.0f), orientation); } } public bool CanLeaderStartMoving() { - foreach (var itr in _members) + foreach (var pair in _members) { - if (itr.Key != _leader && itr.Key.IsAlive()) + if (pair.Key != _leader && pair.Key.IsAlive()) { - if (itr.Key.IsEngaged() || itr.Key.IsReturningHome()) + if (pair.Key.IsEngaged() || pair.Key.IsReturningHome()) return false; } } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 4b29fc484..0dc683af4 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -2120,11 +2120,7 @@ namespace Game.Entities return; // stop taxi flight at summon - if (IsInFlight()) - { - GetMotionMaster().MovementExpired(); - CleanupAfterTaxiFlight(); - } + FinishTaxiFlight(); // drop flag at summon // this code can be reached only when GM is summoning player who carries flag, because player should be immune to summoning spells when he carries flag @@ -6880,9 +6876,18 @@ namespace Game.Entities return ActivateTaxiPathTo(nodes, null, spellid); } + public void FinishTaxiFlight() + { + if (!IsInFlight()) + return; + + GetMotionMaster().Remove(MovementGeneratorType.Flight); + m_taxi.ClearTaxiDestinations(); // not destinations, clear source node + } + public void CleanupAfterTaxiFlight() { - m_taxi.ClearTaxiDestinations(); // not destinations, clear source node + m_taxi.ClearTaxiDestinations(); // not destinations, clear source node Dismount(); RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); } @@ -7206,13 +7211,8 @@ namespace Game.Entities public void SetMover(Unit target) { m_unitMovedByMe.m_playerMovingMe = null; - if (m_unitMovedByMe.IsCreature()) - m_unitMovedByMe.GetMotionMaster().Initialize(); - m_unitMovedByMe = target; m_unitMovedByMe.m_playerMovingMe = this; - if (m_unitMovedByMe.IsCreature()) - m_unitMovedByMe.GetMotionMaster().Initialize(); MoveSetActiveMover packet = new(); packet.MoverGUID = target.GetGUID(); diff --git a/Source/Game/Entities/Unit/Unit.Movement.cs b/Source/Game/Entities/Unit/Unit.Movement.cs index f4d0d0081..b8c057e7f 100644 --- a/Source/Game/Entities/Unit/Unit.Movement.cs +++ b/Source/Game/Entities/Unit/Unit.Movement.cs @@ -172,11 +172,11 @@ namespace Game.Entities if (MotionMaster.IsInvalidMovementSlot(slot)) return; - IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(slot); + MovementGenerator movementGenerator = GetMotionMaster().GetCurrentMovementGenerator(slot); if (movementGenerator != null) movementGenerator.Pause(timer); - if (forced) + if (forced && GetMotionMaster().GetCurrentSlot() == slot) StopMoving(); } @@ -185,7 +185,7 @@ namespace Game.Entities if (MotionMaster.IsInvalidMovementSlot(slot)) return; - IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(slot); + MovementGenerator movementGenerator = GetMotionMaster().GetCurrentMovementGenerator(slot); if (movementGenerator != null) movementGenerator.Resume(timer); } @@ -208,7 +208,7 @@ namespace Game.Entities init.DisableTransportPathTransformations(); // It makes no sense to target global orientation init.SetFacing(ori); - //GetMotionMaster().LaunchMoveSpline(init, EventId.Face, MovementSlot.Controlled); + //GetMotionMaster().LaunchMoveSpline(init, EventId.Face, MovementGeneratorPriority.Highest); init.Launch(); } @@ -223,7 +223,7 @@ namespace Game.Entities init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ(), false); init.SetFacing(GetAbsoluteAngle(obj)); // when on transport, GetAbsoluteAngle will still return global coordinates (and angle) that needs transforming - //GetMotionMaster().LaunchMoveSpline(init, EventId.Face, MovementSlot.Controlled); + //GetMotionMaster().LaunchMoveSpline(init, EventId.Face, MovementGeneratorPriority.Highest); init.Launch(); } @@ -232,7 +232,7 @@ namespace Game.Entities MoveSplineInit init = new(this); init.MoveTo(x, y, z, generatePath, forceDestination); init.SetVelocity(speed); - GetMotionMaster().LaunchMoveSpline(init, 0, MovementSlot.Active, MovementGeneratorType.Point); + GetMotionMaster().LaunchMoveSpline(init, 0, MovementGeneratorPriority.Normal, MovementGeneratorType.Point); } public void KnockbackFrom(float x, float y, float speedXY, float speedZ, SpellEffectExtraData spellEffectExtraData = null) @@ -539,7 +539,7 @@ namespace Game.Entities { if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Follow) { - Unit followed = ((AbstractFollower)GetMotionMaster().Top()).GetTarget(); + Unit followed = (GetMotionMaster().GetCurrentMovementGenerator() as FollowMovementGenerator).GetTarget(); if (followed != null && followed.GetGUID() == GetOwnerGUID() && !followed.IsInCombat()) { float ownerSpeed = followed.GetSpeedRate(mtype); @@ -1254,8 +1254,7 @@ namespace Game.Entities { if (IsAlive()) { - if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Fleeing) - GetMotionMaster().MovementExpired(); + GetMotionMaster().Remove(MovementGeneratorType.Fleeing); if (GetVictim() != null) SetTarget(GetVictim().GetGUID()); } @@ -1280,8 +1279,7 @@ namespace Game.Entities { if (IsAlive()) { - if (GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Confused) - GetMotionMaster().MovementExpired(); + GetMotionMaster().Remove(MovementGeneratorType.Confused); if (GetVictim() != null) SetTarget(GetVictim().GetGUID()); } diff --git a/Source/Game/Entities/Unit/Unit.Pets.cs b/Source/Game/Entities/Unit/Unit.Pets.cs index 2387616a8..e85bce05e 100644 --- a/Source/Game/Entities/Unit/Unit.Pets.cs +++ b/Source/Game/Entities/Unit/Unit.Pets.cs @@ -353,11 +353,8 @@ namespace Game.Entities if (IsTypeId(TypeId.Unit)) { - IMovementGenerator movementGenerator = GetMotionMaster().GetMotionSlot(MovementSlot.Idle); - if (movementGenerator != null) - movementGenerator.Pause(0); - - GetMotionMaster().Clear(MovementSlot.Active); + PauseMovement(0, 0, false); + GetMotionMaster().Clear(MovementGeneratorPriority.Normal); StopMoving(); @@ -476,7 +473,7 @@ namespace Game.Entities RestoreFaction(); ///@todo Handle SLOT_IDLE motion resume - GetMotionMaster().InitDefault(); + GetMotionMaster().InitializeDefault(); Creature creature = ToCreature(); if (creature) @@ -547,8 +544,6 @@ namespace Game.Entities player.SetClientControl(this, true); } - EngageWithTarget(charmer); - // a guardian should always have charminfo if (playerCharmer && this != charmer.GetFirstControlled()) playerCharmer.SendRemoveControlBar(); diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 830e8bc6e..af48ff924 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -171,7 +171,7 @@ namespace Game.Entities } UpdateSplineMovement(diff); - GetMotionMaster().UpdateMotion(diff); + GetMotionMaster().Update(diff); } void _UpdateSpells(uint diff) @@ -999,7 +999,7 @@ namespace Game.Entities init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false); init.SetFacing(GetOrientation()); init.SetTransportExit(); - GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleExit, MovementSlot.Controlled); + GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleExit, MovementGeneratorPriority.Highest); if (player != null) player.ResummonPetTemporaryUnSummonedIfAny(); @@ -1353,7 +1353,7 @@ namespace Game.Entities // * Using 'call pet' on dead pets // * Using 'call stabled pet' // * Logging in with dead pets - GetMotionMaster().Clear(false); + GetMotionMaster().Clear(); GetMotionMaster().MoveIdle(); } StopMoving(); diff --git a/Source/Game/Entities/Vehicle.cs b/Source/Game/Entities/Vehicle.cs index 5a2086a8e..2de86dab4 100644 --- a/Source/Game/Entities/Vehicle.cs +++ b/Source/Game/Entities/Vehicle.cs @@ -641,7 +641,7 @@ namespace Game.Entities init.MoveTo(veSeat.AttachmentOffset.X, veSeat.AttachmentOffset.Y, veSeat.AttachmentOffset.Z, false, true); init.SetFacing(0.0f); init.SetTransportEnter(); - Passenger.GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleBoard, MovementSlot.Controlled); + Passenger.GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleBoard, MovementGeneratorPriority.Highest); Creature creature = Target.GetBase().ToCreature(); if (creature != null) diff --git a/Source/Game/Handlers/BattleGroundHandler.cs b/Source/Game/Handlers/BattleGroundHandler.cs index c9d90ad3d..04aba6d8a 100644 --- a/Source/Game/Handlers/BattleGroundHandler.cs +++ b/Source/Game/Handlers/BattleGroundHandler.cs @@ -345,11 +345,7 @@ namespace Game GetPlayer().SpawnCorpseBones(); } // stop taxi flight at port - if (GetPlayer().IsInFlight()) - { - GetPlayer().GetMotionMaster().MovementExpired(); - GetPlayer().CleanupAfterTaxiFlight(); - } + GetPlayer().FinishTaxiFlight(); BattlefieldStatusActive battlefieldStatus; Global.BattlegroundMgr.BuildBattlegroundStatusActive(out battlefieldStatus, bg, GetPlayer(), battlefieldPort.Ticket.Id, GetPlayer().GetBattlegroundQueueJoinTime(bgQueueTypeId), bg.GetArenaType()); diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index d358d44c9..a845aca58 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -324,22 +324,21 @@ namespace Game } // flight fast teleport case - if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + if (GetPlayer().IsInFlight()) { if (!GetPlayer().InBattleground()) { if (!seamlessTeleport) { // short preparations to continue flight - IMovementGenerator movementGenerator = GetPlayer().GetMotionMaster().Top(); + MovementGenerator movementGenerator = GetPlayer().GetMotionMaster().GetCurrentMovementGenerator(); movementGenerator.Initialize(GetPlayer()); } return; } // Battlegroundstate prepare, stop flight - GetPlayer().GetMotionMaster().MovementExpired(); - GetPlayer().CleanupAfterTaxiFlight(); + GetPlayer().FinishTaxiFlight(); } // resurrect character at enter into instance where his corpse exist after add to map @@ -719,13 +718,12 @@ namespace Game TaxiNodesRecord curDestNode = CliDB.TaxiNodesStorage.LookupByKey(curDest); // far teleport case - if (curDestNode != null && curDestNode.ContinentID != GetPlayer().GetMapId()) + if (curDestNode != null && curDestNode.ContinentID != GetPlayer().GetMapId() && GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) { - if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + FlightPathMovementGenerator flight = GetPlayer().GetMotionMaster().GetCurrentMovementGenerator() as FlightPathMovementGenerator; + if (flight != null) { // short preparations to continue flight - FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().Top(); - flight.SetCurrentNodeAfterTeleport(); TaxiPathNodeRecord node = flight.GetPath()[(int)flight.GetCurrentNode()]; flight.SkipCurrentNode(); diff --git a/Source/Game/Handlers/PetHandler.cs b/Source/Game/Handlers/PetHandler.cs index 51c93a7e9..6a6089340 100644 --- a/Source/Game/Handlers/PetHandler.cs +++ b/Source/Game/Handlers/PetHandler.cs @@ -141,7 +141,7 @@ namespace Game { case CommandStates.Stay: //flat=1792 //STAY pet.StopMoving(); - pet.GetMotionMaster().Clear(false); + pet.GetMotionMaster().Clear(); pet.GetMotionMaster().MoveIdle(); charmInfo.SetCommandState(CommandStates.Stay); @@ -249,7 +249,7 @@ namespace Game break; case CommandStates.MoveTo: pet.StopMoving(); - pet.GetMotionMaster().Clear(false); + pet.GetMotionMaster().Clear(); pet.GetMotionMaster().MovePoint(0, x, y, z); charmInfo.SetCommandState(CommandStates.MoveTo); diff --git a/Source/Game/Handlers/TaxiHandler.cs b/Source/Game/Handlers/TaxiHandler.cs index 8f9aebce4..63eabe9eb 100644 --- a/Source/Game/Handlers/TaxiHandler.cs +++ b/Source/Game/Handlers/TaxiHandler.cs @@ -130,7 +130,7 @@ namespace Game if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); - GetPlayer().GetMotionMaster().Clear(MovementSlot.Controlled); + GetPlayer().GetMotionMaster().Remove(MovementGeneratorType.Flight); if (mountDisplayId != 0) GetPlayer().Mount(mountDisplayId); @@ -235,11 +235,11 @@ namespace Game [WorldPacketHandler(ClientOpcodes.TaxiRequestEarlyLanding, Processing = PacketProcessing.ThreadSafe)] void HandleTaxiRequestEarlyLanding(TaxiRequestEarlyLanding taxiRequestEarlyLanding) { - if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + FlightPathMovementGenerator flight = GetPlayer().GetMotionMaster().GetCurrentMovementGenerator() as FlightPathMovementGenerator; + if (flight != null) { if (GetPlayer().m_taxi.RequestEarlyLanding()) { - FlightPathMovementGenerator flight = (FlightPathMovementGenerator)GetPlayer().GetMotionMaster().Top(); flight.LoadPath(GetPlayer(), flight.GetPath()[(int)flight.GetCurrentNode()].NodeIndex); flight.Reset(GetPlayer()); } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index c705f89ae..0201144e8 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -4875,7 +4875,6 @@ namespace Game.Maps if (!cSource.IsAlive()) return; - cSource.GetMotionMaster().MovementExpired(); cSource.GetMotionMaster().MoveIdle(); switch ((MovementGeneratorType)step.script.Movement.MovementType) diff --git a/Source/Game/Movement/Generators/ChaseMovementGenerator.cs b/Source/Game/Movement/Generators/ChaseMovementGenerator.cs index 35ff5afd2..2143aab74 100644 --- a/Source/Game/Movement/Generators/ChaseMovementGenerator.cs +++ b/Source/Game/Movement/Generators/ChaseMovementGenerator.cs @@ -22,7 +22,7 @@ using System; namespace Game.Movement { - class ChaseMovementGenerator : AbstractFollower, IMovementGenerator + class ChaseMovementGenerator : MovementGenerator { static uint RANGE_CHECK_INTERVAL = 100; // time (ms) until we attempt to recalculate @@ -30,32 +30,49 @@ namespace Game.Movement ChaseAngle? _angle; PathGenerator _path; - Position _lastTargetPosition; + Position _lastTargetPosition = new(); uint _rangeCheckTimer = RANGE_CHECK_INTERVAL; bool _movingTowards = true; bool _mutualChase = true; - public ChaseMovementGenerator(Unit target, ChaseRange? range, ChaseAngle? angle) : base(target) + AbstractFollower _abstractFollower; + + public ChaseMovementGenerator(Unit target, ChaseRange? range, ChaseAngle? angle) { + _abstractFollower = new AbstractFollower(target); _range = range; _angle = angle; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Chase; } - public void Initialize(Unit owner) + public override void Initialize(Unit owner) { - owner.AddUnitState(UnitState.Chase); + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + owner.SetWalk(false); _path = null; + _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) + { + RemoveFlag(MovementGeneratorFlags.Deactivated); + Initialize(owner); + } + + public override bool Update(Unit owner, uint diff) { // owner might be dead or gone (can we even get nullptr here?) if (!owner || !owner.IsAlive()) return false; // our target might have gone away - Unit target = GetTarget(); + Unit target = _abstractFollower.GetTarget(); if (!target) return false; @@ -154,6 +171,7 @@ namespace Game.Movement { if (cOwner) cOwner.SetCannotReachTarget(true); + owner.StopMoving(); return true; } @@ -178,26 +196,34 @@ namespace Game.Movement return true; } - public void Finalize(Unit owner) + public override void Deactivate(Unit owner) { - owner.ClearUnitState(UnitState.Chase | UnitState.ChaseMove); - Creature cOwner = owner.ToCreature(); - if (cOwner != null) - cOwner.SetCannotReachTarget(false); + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.ChaseMove); } - public void Reset(Unit owner) { Initialize(owner); } + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + { + owner.ClearUnitState(UnitState.ChaseMove); + Creature cOwner = owner.ToCreature(); + if (cOwner != null) + cOwner.SetCannotReachTarget(false); + } + } - public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Chase; } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Chase; } - public void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } + public override void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } static bool IsMutualChase(Unit owner, Unit target) { if (target.GetMotionMaster().GetCurrentMovementGeneratorType() != MovementGeneratorType.Chase) return false; - return ((ChaseMovementGenerator)target.GetMotionMaster().Top()).GetTarget() == owner; + return ((ChaseMovementGenerator)target.GetMotionMaster().GetCurrentMovementGenerator())._abstractFollower.GetTarget() == owner; } static bool PositionOkay(Unit owner, Unit target, float? minDistance, float? maxDistance, ChaseAngle? angle) @@ -212,13 +238,12 @@ namespace Game.Movement static void DoMovementInform(Unit owner, Unit target) { - Creature cOwner = owner.ToCreature(); - if (cOwner != null) - { - CreatureAI ai = cOwner.GetAI(); - if (ai != null) - ai.MovementInform(MovementGeneratorType.Chase, (uint)target.GetGUID().GetCounter()); - } + if (!owner.IsCreature()) + return; + + Creature creatureOwner = owner.ToCreature(); + if (creatureOwner.IsAIEnabled && creatureOwner.GetAI() != null) + creatureOwner.GetAI().MovementInform(MovementGeneratorType.Chase, (uint)target.GetGUID().GetCounter()); } } } diff --git a/Source/Game/Movement/Generators/ConfusedGenerator.cs b/Source/Game/Movement/Generators/ConfusedGenerator.cs index ec228bdc8..e3354f834 100644 --- a/Source/Game/Movement/Generators/ConfusedGenerator.cs +++ b/Source/Game/Movement/Generators/ConfusedGenerator.cs @@ -26,14 +26,23 @@ namespace Game.Movement public ConfusedGenerator() { _timer = new TimeTracker(); + _reference = new(); + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Highest; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Confused; } public override void DoInitialize(T owner) { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + if (!owner || !owner.IsAlive()) return; - owner.AddUnitState(UnitState.Confused); + // TODO: UNIT_FIELD_FLAGS should not be handled by generators owner.AddUnitFlag(UnitFlags.Confused); owner.StopMoving(); @@ -44,6 +53,7 @@ namespace Game.Movement public override void DoReset(T owner) { + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); DoInitialize(owner); } @@ -54,20 +64,19 @@ namespace Game.Movement if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); _path = null; return true; } else - _interrupt = false; + RemoveFlag(MovementGeneratorFlags.Interrupted); // waiting for next move _timer.Update(diff); - if (!_interrupt && _timer.Passed() && owner.MoveSpline.Finalized()) + if ((HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized()) || (_timer.Passed() && owner.MoveSpline.Finalized())) { - // start moving - owner.AddUnitState(UnitState.ConfusedMove); + RemoveFlag(MovementGeneratorFlags.Transitory); Position destination = new(_reference); float distance = (float)(4.0f * RandomHelper.FRand(0.0f, 1.0f) - 2.0f); @@ -87,6 +96,8 @@ namespace Game.Movement return true; } + owner.AddUnitState(UnitState.ConfusedMove); + MoveSplineInit init = new(owner); init.MovebyPath(_path.GetPath()); init.SetWalk(true); @@ -97,20 +108,31 @@ namespace Game.Movement return true; } - public override void DoFinalize(T owner) + public override void DoDeactivate(T owner) { - if (owner.IsTypeId(TypeId.Player)) + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.ConfusedMove); + } + + public override void DoFinalize(T owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + + if (active) { - owner.RemoveUnitFlag(UnitFlags.Confused); - owner.ClearUnitState(UnitState.Confused); - owner.StopMoving(); - } - else if (owner.IsTypeId(TypeId.Unit)) - { - owner.RemoveUnitFlag(UnitFlags.Confused); - owner.ClearUnitState(UnitState.Confused | UnitState.ConfusedMove); - if (owner.GetVictim()) - owner.SetTarget(owner.GetVictim().GetGUID()); + if (owner.IsPlayer()) + { + owner.RemoveUnitFlag(UnitFlags.Confused); + owner.StopMoving(); + } + + else + { + owner.RemoveUnitFlag(UnitFlags.Confused); + owner.ClearUnitState(UnitState.ConfusedMove); + if (owner.GetVictim()) + owner.SetTarget(owner.GetVictim().GetGUID()); + } } } @@ -119,9 +141,10 @@ namespace Game.Movement return MovementGeneratorType.Confused; } + public override void UnitSpeedChanged() { AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } + PathGenerator _path; TimeTracker _timer; Position _reference; - bool _interrupt; } } diff --git a/Source/Game/Movement/Generators/FleeingGenerator.cs b/Source/Game/Movement/Generators/FleeingGenerator.cs index a7ec92ef9..8b404056e 100644 --- a/Source/Game/Movement/Generators/FleeingGenerator.cs +++ b/Source/Game/Movement/Generators/FleeingGenerator.cs @@ -30,21 +30,30 @@ namespace Game.Movement { _fleeTargetGUID = fright; _timer = new TimeTracker(); + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Highest; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Fleeing; } public override void DoInitialize(T owner) { - if (owner == null) + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + + if (owner == null || !owner.IsAlive()) return; + // TODO: UNIT_FIELD_FLAGS should not be handled by generators owner.AddUnitFlag(UnitFlags.Fleeing); - owner.AddUnitState(UnitState.Fleeing); - SetTargetLocation(owner); _path = null; + SetTargetLocation(owner); } public override void DoReset(T owner) { + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); DoInitialize(owner); } @@ -55,54 +64,66 @@ namespace Game.Movement if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); _path = null; return true; } else - _interrupt = false; + RemoveFlag(MovementGeneratorFlags.Interrupted); _timer.Update(diff); - if (!_interrupt && _timer.Passed() && owner.MoveSpline.Finalized()) + if ((HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized()) || (_timer.Passed() && owner.MoveSpline.Finalized())) + { + RemoveFlag(MovementGeneratorFlags.Transitory); SetTargetLocation(owner); + } return true; } - public override void DoFinalize(T owner) + public override void DoDeactivate(T owner) { - if (owner.IsTypeId(TypeId.Player)) + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.FleeingMove); + } + + public override void DoFinalize(T owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + + if (active) { - owner.RemoveUnitFlag(UnitFlags.Fleeing); - owner.ClearUnitState(UnitState.Fleeing); - owner.StopMoving(); - } - else - { - owner.RemoveUnitFlag(UnitFlags.Fleeing); - owner.ClearUnitState(UnitState.Fleeing | UnitState.FleeingMove); - if (owner.GetVictim() != null) - owner.SetTarget(owner.GetVictim().GetGUID()); + if (owner.IsPlayer()) + { + owner.RemoveUnitFlag(UnitFlags.Fleeing); + owner.ClearUnitState(UnitState.FleeingMove); + owner.StopMoving(); + } + else + { + owner.RemoveUnitFlag(UnitFlags.Fleeing); + owner.ClearUnitState(UnitState.FleeingMove); + if (owner.GetVictim() != null) + owner.SetTarget(owner.GetVictim().GetGUID()); + } } } void SetTargetLocation(T owner) { - if (owner == null) + if (owner == null || !owner.IsAlive()) return; if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); _path = null; return; } - owner.AddUnitState(UnitState.FleeingMove); - - Position destination = new Position(owner.GetPosition()); + Position destination = new (owner.GetPosition()); GetPoint(owner, destination); // Add LOS check for target point @@ -125,6 +146,8 @@ namespace Game.Movement return; } + owner.AddUnitState(UnitState.FleeingMove); + MoveSplineInit init = new(owner); init.MovebyPath(_path.GetPath()); init.SetWalk(false); @@ -175,10 +198,14 @@ namespace Game.Movement return MovementGeneratorType.Fleeing; } + public override void UnitSpeedChanged() + { + AddFlag(MovementGeneratorFlags.SpeedUpdatePending); + } + PathGenerator _path; ObjectGuid _fleeTargetGUID; TimeTracker _timer; - bool _interrupt; } public class TimedFleeingGenerator : FleeingGenerator @@ -188,10 +215,25 @@ namespace Game.Movement _totalFleeTime = new TimeTracker(time); } - public override void Finalize(Unit owner) + public override bool Update(Unit owner, uint diff) { + if (owner == null || !owner.IsAlive()) + return false; + + _totalFleeTime.Update(diff); + if (_totalFleeTime.Passed()) + return false; + + return DoUpdate(owner.ToCreature(), diff); + } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (!active) + return; + owner.RemoveUnitFlag(UnitFlags.Fleeing); - owner.ClearUnitState(UnitState.Fleeing | UnitState.FleeingMove); Unit victim = owner.GetVictim(); if (victim != null) { @@ -203,26 +245,6 @@ namespace Game.Movement } } - public override bool Update(Unit owner, uint diff) - { - if (!owner.IsAlive()) - return false; - - if (owner.HasUnitState(UnitState.Root | UnitState.Stunned)) - { - owner.ClearUnitState(UnitState.FleeingMove); - return true; - } - - _totalFleeTime.Update(diff); - if (_totalFleeTime.Passed()) - return false; - - // This calls grant-parent Update method hiden by FleeingMovementGenerator.Update(Creature &, uint32) version - // This is done instead of casting Unit& to Creature& and call parent method, then we can use Unit directly - return base.Update(owner, diff); - } - public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.TimedFleeing; diff --git a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs index 752908f95..37bd3dbda 100644 --- a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs @@ -28,6 +28,116 @@ namespace Game.Movement { public class FlightPathMovementGenerator : MovementGeneratorMedium { + public FlightPathMovementGenerator() + { + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Highest; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.InFlight; + } + + public override void DoInitialize(Player owner) + { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + + DoReset(owner); + InitEndGridInfo(); + } + + public override void DoReset(Player owner) + { + RemoveFlag(MovementGeneratorFlags.Deactivated); + + owner.CombatStopWithPets(); + owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); + + MoveSplineInit init = new(owner); + uint end = GetPathAtMapEnd(); + init.args.path = new Vector3[end]; + for (int i = (int)GetCurrentNode(); i != end; ++i) + { + Vector3 vertice = new(_path[i].Loc.X, _path[i].Loc.Y, _path[i].Loc.Z); + init.args.path[i] = vertice; + } + init.SetFirstPointId((int)GetCurrentNode()); + init.SetFly(); + init.SetSmooth(); + init.SetUncompressed(); + init.SetWalk(true); + init.SetVelocity(30.0f); + init.Launch(); + } + + public override bool DoUpdate(Player owner, uint diff) + { + if (owner == null) + return false; + + uint pointId = (uint)(owner.MoveSpline.CurrentPathIdx() < 0 ? 0 : owner.MoveSpline.CurrentPathIdx()); + if (pointId > _currentNode) + { + bool departureEvent = true; + do + { + DoEventIfAny(owner, _path[_currentNode], departureEvent); + while (!_pointsForPathSwitch.Empty() && _pointsForPathSwitch[0].PathIndex <= _currentNode) + { + _pointsForPathSwitch.RemoveAt(0); + owner.m_taxi.NextTaxiDestination(); + if (!_pointsForPathSwitch.Empty()) + { + owner.UpdateCriteria(CriteriaType.MoneySpentOnTaxis, (uint)_pointsForPathSwitch[0].Cost); + owner.ModifyMoney(-_pointsForPathSwitch[0].Cost); + } + } + + if (pointId == _currentNode) + break; + + if (_currentNode == _preloadTargetNode) + PreloadEndGrid(); + + _currentNode += (departureEvent ? 1 : 0); + departureEvent = !departureEvent; + } + while (true); + } + + if (_currentNode >= (_path.Count - 1)) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; + } + + public override void DoDeactivate(Player owner) + { + AddFlag(MovementGeneratorFlags.Deactivated); + } + + public override void DoFinalize(Player owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (!active) + return; + + owner.Dismount(); + owner.RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); + + if (owner.m_taxi.Empty()) + { + // update z position to ground and orientation for landing point + // this prevent cheating with landing point at lags + // when client side flight end early in comparison server side + owner.StopMoving(); + owner.SetFallInformation(0, owner.GetPositionZ()); + } + + owner.RemovePlayerFlag(PlayerFlags.TaxiBenchmark); + } + uint GetPathAtMapEnd() { if (_currentNode >= _path.Count) @@ -92,90 +202,6 @@ namespace Game.Movement } } - public override void DoInitialize(Player owner) - { - Reset(owner); - InitEndGridInfo(); - } - - public override void DoFinalize(Player owner) - { - // remove flag to prevent send object build movement packets for flight state and crash (movement generator already not at top of stack) - owner.ClearUnitState(UnitState.InFlight); - - owner.Dismount(); - owner.RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); - - if (owner.m_taxi.Empty()) - { - // update z position to ground and orientation for landing point - // this prevent cheating with landing point at lags - // when client side flight end early in comparison server side - owner.StopMoving(); - owner.SetFallInformation(0, owner.GetPositionZ()); - } - - owner.RemovePlayerFlag(PlayerFlags.TaxiBenchmark); - owner.RestoreDisplayId(); - } - - public override void DoReset(Player owner) - { - owner.AddUnitState(UnitState.InFlight); - owner.CombatStopWithPets(); - owner.AddUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.TaxiFlight); - - MoveSplineInit init = new(owner); - uint end = GetPathAtMapEnd(); - init.args.path = new Vector3[end]; - for (int i = (int)GetCurrentNode(); i != end; ++i) - { - Vector3 vertice = new(_path[i].Loc.X, _path[i].Loc.Y, _path[i].Loc.Z); - init.args.path[i] = vertice; - } - init.SetFirstPointId((int)GetCurrentNode()); - init.SetFly(); - init.SetSmooth(); - init.SetUncompressed(); - init.SetWalk(true); - init.SetVelocity(30.0f); - init.Launch(); - } - - public override bool DoUpdate(Player player, uint time_diff) - { - uint pointId = (uint)player.MoveSpline.CurrentPathIdx(); - if (pointId > _currentNode) - { - bool departureEvent = true; - do - { - DoEventIfAny(player, _path[_currentNode], departureEvent); - while (!_pointsForPathSwitch.Empty() && _pointsForPathSwitch[0].PathIndex <= _currentNode) - { - _pointsForPathSwitch.RemoveAt(0); - player.m_taxi.NextTaxiDestination(); - if (!_pointsForPathSwitch.Empty()) - { - player.UpdateCriteria(CriteriaType.MoneySpentOnTaxis, (uint)_pointsForPathSwitch[0].Cost); - player.ModifyMoney(-_pointsForPathSwitch[0].Cost); - } - } - - if (pointId == _currentNode) - break; - - if (_currentNode == _preloadTargetNode) - PreloadEndGrid(); - _currentNode += (departureEvent ? 1 : 0); - departureEvent = !departureEvent; - } - while (true); - } - - return _currentNode < (_path.Count - 1); - } - public void SetCurrentNodeAfterTeleport() { if (_path.Empty() || _currentNode >= _path.Count) @@ -193,25 +219,16 @@ namespace Game.Movement } - void DoEventIfAny(Player player, TaxiPathNodeRecord node, bool departure) + void DoEventIfAny(Player owner, TaxiPathNodeRecord node, bool departure) { uint eventid = departure ? node.DepartureEventID : node.ArrivalEventID; if (eventid != 0) { - Log.outDebug(LogFilter.Scripts, "Taxi {0} event {1} of node {2} of path {3} for player {4}", departure ? "departure" : "arrival", eventid, node.NodeIndex, node.PathID, player.GetName()); - player.GetMap().ScriptsStart(ScriptsType.Event, eventid, player, player); + Log.outDebug(LogFilter.MapsScript, $"FlightPathMovementGenerator::DoEventIfAny: taxi {(departure ? "departure" : "arrival")} event {eventid} of node {node.NodeIndex} of path {node.PathID} for player {owner.GetName()}"); + owner.GetMap().ScriptsStart(ScriptsType.Event, eventid, owner, owner); } } - bool GetResetPos(Player player, out float x, out float y, out float z) - { - TaxiPathNodeRecord node = _path[_currentNode]; - x = node.Loc.X; - y = node.Loc.Y; - z = node.Loc.Z; - return true; - } - void InitEndGridInfo() { int nodeCount = _path.Count; //! Number of nodes in path. @@ -223,24 +240,37 @@ namespace Game.Movement void PreloadEndGrid() { - // used to preload the final grid where the flightmaster is + // Used to preload the final grid where the flightmaster is Map endMap = Global.MapMgr.FindBaseNonInstanceMap(_endMapId); // 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, _path.Count - 1); + Log.outDebug(LogFilter.Server, "FlightPathMovementGenerator::PreloadEndGrid: 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"); + Log.outDebug(LogFilter.Server, "FlightPathMovementGenerator::PreloadEndGrid: Unable to determine map to preload flightmaster grid"); + } + + + + + + public override bool GetResetPosition(Unit u, out float x, out float y, out float z) + { + var node = _path[_currentNode]; + x = node.Loc.X; + y = node.Loc.Y; + z = node.Loc.Z; + return true; } public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Flight; } public List GetPath() { return _path; } - bool HasArrived() { return (_currentNode >= _path.Count); } + bool HasArrived() { return _currentNode >= _path.Count; } public void SkipCurrentNode() { ++_currentNode; } diff --git a/Source/Game/Movement/Generators/FollowMovementGenerator.cs b/Source/Game/Movement/Generators/FollowMovementGenerator.cs index 6f952833a..2be72fe82 100644 --- a/Source/Game/Movement/Generators/FollowMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FollowMovementGenerator.cs @@ -22,7 +22,7 @@ using System; namespace Game.Movement { - public class FollowMovementGenerator : AbstractFollower, IMovementGenerator + public class FollowMovementGenerator : MovementGenerator { static uint CHECK_INTERVAL = 500; static float FOLLOW_RANGE_TOLERANCE = 1.0f; @@ -34,27 +34,46 @@ namespace Game.Movement PathGenerator _path; Position _lastTargetPosition; - public FollowMovementGenerator(Unit target, float range, ChaseAngle angle) : base(target) + AbstractFollower _abstractFollower; + + public FollowMovementGenerator(Unit target, float range, ChaseAngle angle) { + _abstractFollower = new AbstractFollower(target); _range = range; _angle = angle; + _lastTargetPosition = new(); + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Follow; } - public void Initialize(Unit owner) + public override void Initialize(Unit owner) { - owner.AddUnitState(UnitState.Follow); + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + + owner.StopMoving(); UpdatePetSpeed(owner); _path = null; + _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) + { + RemoveFlag(MovementGeneratorFlags.Deactivated); + Initialize(owner); + } + + public override bool Update(Unit owner, uint diff) { // owner might be dead or gone - if (!owner.IsAlive()) + if (owner == null || !owner.IsAlive()) return false; // our target might have gone away - Unit target = GetTarget(); + Unit target = _abstractFollower.GetTarget(); if (target == null) return false; @@ -144,10 +163,20 @@ namespace Game.Movement return true; } - public void Finalize(Unit owner) + public override void Deactivate(Unit owner) { - owner.ClearUnitState(UnitState.Follow | UnitState.FollowMove); - UpdatePetSpeed(owner); + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.FollowMove); + } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + { + owner.ClearUnitState(UnitState.FollowMove); + UpdatePetSpeed(owner); + } } void UpdatePetSpeed(Unit owner) @@ -155,7 +184,7 @@ namespace Game.Movement Pet oPet = owner.ToPet(); if (oPet != null) { - if (!GetTarget() || GetTarget().GetGUID() == owner.GetOwnerGUID()) + if (!_abstractFollower.GetTarget() || _abstractFollower.GetTarget().GetGUID() == owner.GetOwnerGUID()) { oPet.UpdateSpeed(UnitMoveType.Run); oPet.UpdateSpeed(UnitMoveType.Walk); @@ -164,11 +193,16 @@ namespace Game.Movement } } - public void Reset(Unit owner) { } + public Unit GetTarget() + { + return _abstractFollower.GetTarget(); + } - public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Follow; } - public void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Follow; } + + public override void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); } static bool PositionOkay(Unit owner, Unit target, float range, ChaseAngle? angle = null) { @@ -183,9 +217,9 @@ namespace Game.Movement if (!owner.IsCreature()) return; - UnitAI ai = owner.GetAI(); - if (ai != null) - ((CreatureAI)ai).MovementInform(MovementGeneratorType.Follow, (uint)target.GetGUID().GetCounter()); + Creature creatureOwner = owner.ToCreature(); + if (creatureOwner.IsAIEnabled && creatureOwner.GetAI() != null) + creatureOwner.GetAI().MovementInform(MovementGeneratorType.Follow, (uint)target.GetGUID().GetCounter()); } } } diff --git a/Source/Game/Movement/Generators/FormationMovement.cs b/Source/Game/Movement/Generators/FormationMovement.cs index 82c384440..c398c6bbd 100644 --- a/Source/Game/Movement/Generators/FormationMovement.cs +++ b/Source/Game/Movement/Generators/FormationMovement.cs @@ -29,15 +29,21 @@ namespace Game.Movement _moveType = moveType; _run = run; _orientation = orientation; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } public override void DoInitialize(Creature owner) { - owner.AddUnitState(UnitState.Roaming); + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); return; } @@ -73,6 +79,8 @@ namespace Game.Movement public override void DoReset(Creature owner) { + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); + owner.StopMoving(); DoInitialize(owner); } @@ -84,15 +92,14 @@ namespace Game.Movement if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); return true; } - if ((_interrupt && owner.MoveSpline.Finalized()) || (_recalculateSpeed && !owner.MoveSpline.Finalized())) + if ((HasFlag(MovementGeneratorFlags.Interrupted) && owner.MoveSpline.Finalized()) || (HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized())) { - _recalculateSpeed = false; - _interrupt = false; + RemoveFlag(MovementGeneratorFlags.Interrupted | MovementGeneratorFlags.SpeedUpdatePending); owner.AddUnitState(UnitState.RoamingMove); @@ -122,20 +129,34 @@ namespace Game.Movement init.Launch(); } - return !owner.MoveSpline.Finalized(); + if (owner.MoveSpline.Finalized()) + { + RemoveFlag(MovementGeneratorFlags.Transitory); + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; } - public override void DoFinalize(Creature owner) + public override void DoDeactivate(Creature owner) { - owner.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } - if (owner.MoveSpline.Finalized()) + public override void DoFinalize(Creature owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + owner.ClearUnitState(UnitState.RoamingMove); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) MovementInform(owner); } - public void UnitSpeedChanged() + public override void UnitSpeedChanged() { - _recalculateSpeed = true; + AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } public override MovementGeneratorType GetMovementGeneratorType() @@ -154,7 +175,5 @@ namespace Game.Movement WaypointMoveType _moveType; bool _run; bool _orientation; - bool _recalculateSpeed; - bool _interrupt; } } diff --git a/Source/Game/Movement/Generators/GenericMovementGenerator.cs b/Source/Game/Movement/Generators/GenericMovementGenerator.cs index c0274ed97..11f2abdb7 100644 --- a/Source/Game/Movement/Generators/GenericMovementGenerator.cs +++ b/Source/Game/Movement/Generators/GenericMovementGenerator.cs @@ -21,7 +21,7 @@ using System; namespace Game.Movement { - class GenericMovementGenerator : IMovementGenerator + class GenericMovementGenerator : MovementGenerator { MoveSplineInit _splineInit; MovementGeneratorType _type; @@ -39,25 +39,59 @@ namespace Game.Movement _duration = new(); _arrivalSpellId = arrivalSpellId; _arrivalSpellTargetGuid = arrivalSpellTargetGuid; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } - public void Initialize(Unit owner) + public override void Initialize(Unit owner) { + if (HasFlag(MovementGeneratorFlags.Deactivated)) // Resume spline is not supported + { + RemoveFlag(MovementGeneratorFlags.Deactivated); + AddFlag(MovementGeneratorFlags.Finalized); + return; + } + + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + _duration.Reset(_splineInit.Launch()); } - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) { - _duration.Update((int)diff); - if (_duration.Passed()) - return false; - - return !owner.MoveSpline.Finalized(); + Initialize(owner); } - public void Finalize(Unit owner) + public override bool Update(Unit owner, uint diff) { - MovementInform(owner); + if (!owner || HasFlag(MovementGeneratorFlags.Finalized)) + return false; + + _duration.Update((int)diff); + if (_duration.Passed() || owner.MoveSpline.Finalized()) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + + return true; + } + + public override void Deactivate(Unit owner) + { + AddFlag(MovementGeneratorFlags.Deactivated); + } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) + MovementInform(owner); } void MovementInform(Unit owner) @@ -66,13 +100,10 @@ namespace Game.Movement owner.CastSpell(Global.ObjAccessor.GetUnit(owner, _arrivalSpellTargetGuid), _arrivalSpellId, true); Creature creature = owner.ToCreature(); - if (creature != null) - if (creature.GetAI() != null) - creature.GetAI().MovementInform(_type, _pointId); + if (creature != null && creature.GetAI() != null) + creature.GetAI().MovementInform(_type, _pointId); } - public void Reset(Unit owner) { } - - public MovementGeneratorType GetMovementGeneratorType() { return _type; } + public override MovementGeneratorType GetMovementGeneratorType() { return _type; } } } diff --git a/Source/Game/Movement/Generators/HomeMovement.cs b/Source/Game/Movement/Generators/HomeMovement.cs index d97f34244..3092730f7 100644 --- a/Source/Game/Movement/Generators/HomeMovement.cs +++ b/Source/Game/Movement/Generators/HomeMovement.cs @@ -23,67 +23,96 @@ namespace Game.AI { public class HomeMovementGenerator : MovementGeneratorMedium where T : Creature { + public HomeMovementGenerator() + { + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; + } + public override void DoInitialize(T owner) { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + SetTargetLocation(owner); } public override void DoReset(T owner) { + RemoveFlag(MovementGeneratorFlags.Deactivated); DoInitialize(owner); } public override bool DoUpdate(T owner, uint diff) { - _arrived = _skipToHome || owner.MoveSpline.Finalized(); - return !_arrived; + if (HasFlag(MovementGeneratorFlags.Interrupted) || owner.MoveSpline.Finalized()) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; } - public override void DoFinalize(T owner) + public override void DoDeactivate(T owner) { - if (_arrived) + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } + + public override void DoFinalize(T owner, bool active, bool movementInform) + { + if (!owner.IsCreature()) + return; + + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + owner.ClearUnitState(UnitState.RoamingMove | UnitState.Evade); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) { - owner.ClearUnitState(UnitState.Evade); owner.SetWalk(true); + owner.SetSpawnHealth(); owner.LoadCreaturesAddon(); owner.GetAI().JustReachedHome(); - owner.SetSpawnHealth(); } } void SetTargetLocation(T owner) { + // if we are ROOT/STUNNED/DISTRACTED even after aura clear, finalize on next update - otherwise we would get stuck in evade if (owner.HasUnitState(UnitState.Root | UnitState.Stunned | UnitState.Distracted)) - { // if we are ROOT/STUNNED/DISTRACTED even after aura clear, finalize on next update - otherwise we would get stuck in evade - _skipToHome = true; + { + AddFlag(MovementGeneratorFlags.Interrupted); return; } + owner.ClearUnitState(UnitState.AllErasable & ~UnitState.Evade); + owner.AddUnitState(UnitState.RoamingMove); + + Position destination = owner.GetHomePosition(); MoveSplineInit init = new(owner); - float x, y, z, o; - // at apply we can select more nice return points base at current movegen - if (owner.GetMotionMaster().Empty() || !owner.GetMotionMaster().Top().GetResetPosition(owner, out x, out y, out z)) - { - owner.GetHomePosition(out x, out y, out z, out o); - init.SetFacing(o); - } - owner.UpdateAllowedPositionZ(x, y, ref z); - init.MoveTo(x, y, z); + /* + * TODO: maybe this never worked, who knows, top is always this generator, so this code calls GetResetPosition on itself + * + * if (owner->GetMotionMaster()->empty() || !owner->GetMotionMaster()->top()->GetResetPosition(owner, x, y, z)) + * { + * owner->GetHomePosition(x, y, z, o); + * init.SetFacing(o); + * } + */ + + owner.UpdateAllowedPositionZ(destination.posX, destination.posY, ref destination.posZ); + init.MoveTo(destination); + init.SetFacing(destination.GetOrientation()); init.SetWalk(false); init.Launch(); - - _skipToHome = false; - _arrived = false; - - owner.ClearUnitState(UnitState.AllErasable & ~UnitState.Evade); } public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Home; } - - bool _arrived; - bool _skipToHome; } } diff --git a/Source/Game/Movement/Generators/IdleMovement.cs b/Source/Game/Movement/Generators/IdleMovement.cs index e0ea8ad71..2147b6995 100644 --- a/Source/Game/Movement/Generators/IdleMovement.cs +++ b/Source/Game/Movement/Generators/IdleMovement.cs @@ -17,147 +17,224 @@ using Framework.Constants; using Game.Entities; +using System; namespace Game.Movement { - public class IdleMovementGenerator : IMovementGenerator + public class IdleMovementGenerator : MovementGenerator { - public void Initialize(Unit owner) + public IdleMovementGenerator() { - Reset(owner); + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.Initialized; + BaseUnitState = 0; } - public void Reset(Unit owner) + public override void Initialize(Unit owner) { - if (!owner.IsStopped()) - owner.StopMoving(); + owner.StopMoving(); } - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) + { + owner.StopMoving(); + } + + public override bool Update(Unit owner, uint diff) { return true; } - public void Finalize(Unit owner) + public override void Deactivate(Unit owner) { } + + public override void Finalize(Unit owner, bool active, bool movementInform) { + AddFlag(MovementGeneratorFlags.Finalized); } - public MovementGeneratorType GetMovementGeneratorType() + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Idle; } - - public bool IsActive { get; set; } } - public class RotateMovementGenerator : IMovementGenerator + public class RotateMovementGenerator : MovementGenerator { - public RotateMovementGenerator(uint time, RotateDirection direction) + public RotateMovementGenerator(uint id, uint time, RotateDirection direction) { + _id = id; _duration = time; _maxDuration = time; _direction = direction; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Rotating; } - public void Initialize(Unit owner) + public override void Initialize(Unit owner) { - if (!owner.IsStopped()) - owner.StopMoving(); + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + + owner.StopMoving(); - if (owner.GetVictim()) - owner.SetInFront(owner.GetVictim()); - - owner.AddUnitState(UnitState.Rotating); - owner.AttackStop(); + /* + * TODO: This code should be handled somewhere else, like MovementInform + * + * if (owner->GetVictim()) + * owner->SetInFront(owner->GetVictim()); + * + * owner->AttackStop(); + */ } - public void Reset(Unit owner) { } - - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) { + RemoveFlag(MovementGeneratorFlags.Deactivated); + Initialize(owner); + } + + public override bool Update(Unit owner, uint diff) + { + if (owner == null) + return false; + float angle = owner.GetOrientation(); angle += diff * MathFunctions.TwoPi / _maxDuration * (_direction == RotateDirection.Left ? 1.0f : -1.0f); - angle = MathFunctions.wrap(angle, 0.0f, MathFunctions.TwoPi); + angle = Math.Clamp(angle, 0.0f, MathF.PI * 2); - owner.SetOrientation(angle); // UpdateSplinePosition does not set orientation with UNIT_STATE_ROTATING - owner.SetFacingTo(angle); // Send spline movement to clients + MoveSplineInit init = new(owner); + init.MoveTo(owner, false); + if (!owner.GetTransGUID().IsEmpty()) + init.DisableTransportPathTransformations(); + + init.SetFacing(angle); + init.Launch(); if (_duration > diff) _duration -= diff; else + { + AddFlag(MovementGeneratorFlags.InformEnabled); return false; + } return true; } - public void Finalize(Unit owner) + public override void Deactivate(Unit owner) { - owner.ClearUnitState(UnitState.Rotating); - if (owner.IsTypeId(TypeId.Unit)) - owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Rotate, 0); + AddFlag(MovementGeneratorFlags.Deactivated); } - public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Rotate; } + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (movementInform && owner.IsCreature()) + owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Rotate, _id); + } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Rotate; } + + uint _id; uint _duration; uint _maxDuration; RotateDirection _direction; } - public class DistractMovementGenerator : IMovementGenerator + public class DistractMovementGenerator : MovementGenerator { - public DistractMovementGenerator(uint timer) + public DistractMovementGenerator(uint timer, float orientation) { _timer = timer; + _orientation = orientation; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Highest; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Distracted; } - public void Initialize(Unit owner) + public override void Initialize(Unit owner) { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + // Distracted creatures stand up if not standing if (!owner.IsStandState()) owner.SetStandState(UnitStandStateType.Stand); - owner.AddUnitState(UnitState.Distracted); + MoveSplineInit init = new(owner); + init.MoveTo(owner, false); + if (!owner.GetTransGUID().IsEmpty()) + init.DisableTransportPathTransformations(); + + init.SetFacing(_orientation); + init.Launch(); } - public void Reset(Unit owner) { } - - public bool Update(Unit owner, uint diff) + public override void Reset(Unit owner) { - if (diff > _timer) + RemoveFlag(MovementGeneratorFlags.Deactivated); + Initialize(owner); + } + + public override bool Update(Unit owner, uint diff) + { + if (owner == null) return false; + if (diff > _timer) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + _timer -= diff; return true; } - public virtual void Finalize(Unit owner) + public override void Deactivate(Unit owner) { - owner.ClearUnitState(UnitState.Distracted); + AddFlag(MovementGeneratorFlags.Deactivated); + } + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + + // TODO: This code should be handled somewhere else // If this is a creature, then return orientation to original position (for idle movement creatures) - if (owner.IsTypeId(TypeId.Unit)) + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled) && owner.IsCreature()) { float angle = owner.ToCreature().GetHomePosition().GetOrientation(); owner.SetFacingTo(angle); } } - public virtual MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Distract; } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Distract; } uint _timer; + float _orientation; } public class AssistanceDistractMovementGenerator : DistractMovementGenerator { - public AssistanceDistractMovementGenerator(uint timer) : base(timer) { } + public AssistanceDistractMovementGenerator(uint timer, float orientation) : base(timer, orientation) + { + Priority = MovementGeneratorPriority.Normal; + } - public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.AssistanceDistract; } - - public override void Finalize(Unit owner) + public override void Finalize(Unit owner, bool active, bool movementInform) { owner.ClearUnitState(UnitState.Distracted); owner.ToCreature().SetReactState(ReactStates.Aggressive); } + + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.AssistanceDistract; } } } diff --git a/Source/Game/Movement/Generators/MovementGenerators.cs b/Source/Game/Movement/Generators/MovementGenerators.cs index 87d7d7979..74cc6e72f 100644 --- a/Source/Game/Movement/Generators/MovementGenerators.cs +++ b/Source/Game/Movement/Generators/MovementGenerators.cs @@ -22,62 +22,86 @@ using Framework.GameMath; namespace Game.Movement { - public interface IMovementGenerator + public abstract class MovementGenerator { - public void Finalize(Unit owner); + public MovementGeneratorMode Mode; + public MovementGeneratorPriority Priority; + public MovementGeneratorFlags Flags; + public UnitState BaseUnitState; - public void Initialize(Unit owner); + // on top first update + public virtual void Initialize(Unit owner) { } - public void Reset(Unit owner); + // on top reassign + public virtual void Reset(Unit owner) { } - public bool Update(Unit owner, uint time_diff); + // on top on MotionMaster::Update + public abstract bool Update(Unit owner, uint diff); - public MovementGeneratorType GetMovementGeneratorType(); + // on current top if another movement replaces + public virtual void Deactivate(Unit owner) { } - public void UnitSpeedChanged() { } + // on movement delete + public virtual void Finalize(Unit owner, bool active, bool movementInform) { } - public void Pause(uint timer = 0) { } + public abstract MovementGeneratorType GetMovementGeneratorType(); - public void Resume(uint overrideTimer = 0) { } + public virtual void UnitSpeedChanged() { } + + // timer in ms + public virtual void Pause(uint timer = 0) { } + + // timer in ms + public virtual void Resume(uint overrideTimer = 0) { } // used by Evade code for select point to evade with expected restart default movement - public bool GetResetPosition(Unit u, out float x, out float y, out float z) + public virtual bool GetResetPosition(Unit u, out float x, out float y, out float z) { x = y = z = 0.0f; return false; } + + public void AddFlag(MovementGeneratorFlags flag) { Flags |= flag; } + public bool HasFlag(MovementGeneratorFlags flag) { return (Flags & flag) != 0; } + public void RemoveFlag(MovementGeneratorFlags flag) { Flags &= ~flag; } } - public abstract class MovementGeneratorMedium : IMovementGenerator where T : Unit + public abstract class MovementGeneratorMedium : MovementGenerator where T : Unit { - public virtual void Initialize(Unit owner) + public override void Initialize(Unit owner) { DoInitialize((T)owner); IsActive = true; } - public virtual void Finalize(Unit owner) - { - DoFinalize((T)owner); - } - - public virtual void Reset(Unit owner) + public override void Reset(Unit owner) { DoReset((T)owner); } - public virtual bool Update(Unit owner, uint diff) + public override bool Update(Unit owner, uint diff) { return DoUpdate((T)owner, diff); } + public override void Deactivate(Unit owner) + { + DoDeactivate((T)owner); + } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + DoFinalize((T)owner, active, movementInform); + } + public bool IsActive { get; set; } public abstract void DoInitialize(T owner); - public abstract void DoFinalize(T owner); + public abstract void DoFinalize(T owner, bool active, bool movementInform); public abstract void DoReset(T owner); public abstract bool DoUpdate(T owner, uint diff); + public abstract void DoDeactivate(T owner); - public virtual MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Max; } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Max; } } } diff --git a/Source/Game/Movement/Generators/PointMovement.cs b/Source/Game/Movement/Generators/PointMovement.cs index 195b37a79..63e08d4c5 100644 --- a/Source/Game/Movement/Generators/PointMovement.cs +++ b/Source/Game/Movement/Generators/PointMovement.cs @@ -22,31 +22,36 @@ namespace Game.Movement { public class PointMovementGenerator : MovementGeneratorMedium where T : Unit { - public PointMovementGenerator(uint id, float x, float y, float z, bool generatePath, float speed = 0.0f, Unit faceTarget = null, SpellEffectExtraData spellEffectExtraData = null, float? finalOrient = null) + public PointMovementGenerator(uint id, float x, float y, float z, bool generatePath, float speed = 0.0f, float? finalOrient = null, Unit faceTarget = null, SpellEffectExtraData spellEffectExtraData = null) { _movementId = id; _destination = new Position(x, y, z); _speed = speed; + _generatePath = generatePath; + _finalOrient = finalOrient; _faceTarget = faceTarget; _spellEffectExtra = spellEffectExtraData; - _generatePath = generatePath; - _recalculateSpeed = false; - _finalOrient = finalOrient; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } public override void DoInitialize(T owner) { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + if (_movementId == EventId.ChargePrepath) { - owner.AddUnitState(UnitState.Roaming | UnitState.RoamingMove); + owner.AddUnitState(UnitState.RoamingMove); return; } - owner.AddUnitState(UnitState.Roaming); - if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); return; } @@ -77,6 +82,8 @@ namespace Game.Movement public override void DoReset(T owner) { + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); + owner.StopMoving(); DoInitialize(owner); } @@ -87,19 +94,25 @@ namespace Game.Movement return false; if (_movementId == EventId.ChargePrepath) - return !owner.MoveSpline.Finalized(); + { + if (owner.MoveSpline.Finalized()) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; + } if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); return true; } - if ((_interrupt && owner.MoveSpline.Finalized()) || (_recalculateSpeed && !owner.MoveSpline.Finalized())) + if ((HasFlag(MovementGeneratorFlags.Interrupted) && owner.MoveSpline.Finalized()) || (HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized())) { - _recalculateSpeed = false; - _interrupt = false; + RemoveFlag(MovementGeneratorFlags.Interrupted | MovementGeneratorFlags.SpeedUpdatePending); owner.AddUnitState(UnitState.RoamingMove); @@ -115,14 +128,28 @@ namespace Game.Movement creature.SignalFormationMovement(_destination, _movementId); } - return !owner.MoveSpline.Finalized(); + if (owner.MoveSpline.Finalized()) + { + RemoveFlag(MovementGeneratorFlags.Transitory); + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; } - public override void DoFinalize(T owner) + public override void DoDeactivate(T owner) { - owner.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } - if (owner.MoveSpline.Finalized()) + public override void DoFinalize(T owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + owner.ClearUnitState(UnitState.RoamingMove); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) MovementInform(owner); } @@ -135,11 +162,13 @@ namespace Game.Movement } } - public void UnitSpeedChanged() + public override void UnitSpeedChanged() { - _recalculateSpeed = true; + AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } + public uint GetId() { return _movementId; } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Point; @@ -148,29 +177,33 @@ namespace Game.Movement uint _movementId; Position _destination; float _speed; - Unit _faceTarget; - SpellEffectExtraData _spellEffectExtra; bool _generatePath; - bool _recalculateSpeed; - bool _interrupt; //! if set then unit will turn to specified _orient in provided _pos float? _finalOrient; + Unit _faceTarget; + SpellEffectExtraData _spellEffectExtra; } public class AssistanceMovementGenerator : PointMovementGenerator { - public AssistanceMovementGenerator(float x, float y, float z) : base(0, x, y, z, true) { } + public AssistanceMovementGenerator(uint id, float x, float y, float z) : base(id, x, y, z, true) { } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + owner.ClearUnitState(UnitState.RoamingMove); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) + { + Creature ownerCreature = owner.ToCreature(); + ownerCreature.SetNoCallAssistance(false); + ownerCreature.CallAssistance(); + if (ownerCreature.IsAlive()) + ownerCreature.GetMotionMaster().MoveSeekAssistanceDistract(WorldConfig.GetUIntValue(WorldCfg.CreatureFamilyAssistanceDelay)); + } + } public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Assistance; } - - public override void Finalize(Unit owner) - { - owner.ClearUnitState(UnitState.Roaming); - owner.StopMoving(); - owner.ToCreature().SetNoCallAssistance(false); - owner.ToCreature().CallAssistance(); - if (owner.IsAlive()) - owner.GetMotionMaster().MoveSeekAssistanceDistract(WorldConfig.GetUIntValue(WorldCfg.CreatureFamilyAssistanceDelay)); - } } } diff --git a/Source/Game/Movement/Generators/RandomMovement.cs b/Source/Game/Movement/Generators/RandomMovement.cs index 0835877fb..46178eda2 100644 --- a/Source/Game/Movement/Generators/RandomMovement.cs +++ b/Source/Game/Movement/Generators/RandomMovement.cs @@ -24,18 +24,26 @@ namespace Game.Movement { public class RandomMovementGenerator : MovementGeneratorMedium { - public RandomMovementGenerator(float spawn_dist = 0.0f) + public RandomMovementGenerator(float spawnDist = 0.0f) { _timer = new TimeTracker(); - _wanderDistance = spawn_dist; + _reference = new(); + _wanderDistance = spawnDist; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } public override void DoInitialize(Creature owner) { + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); + if (owner == null || !owner.IsAlive()) return; - owner.AddUnitState(UnitState.Roaming); _reference = owner.GetPosition(); owner.StopMoving(); @@ -48,36 +56,52 @@ namespace Game.Movement public override void DoReset(Creature owner) { + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); DoInitialize(owner); } public override bool DoUpdate(Creature owner, uint diff) { if (!owner || !owner.IsAlive()) - return false; + return true; if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); _path = null; return true; } else - _interrupt = false; + RemoveFlag(MovementGeneratorFlags.Interrupted); _timer.Update(diff); - if (!_interrupt && _timer.Passed() && owner.MoveSpline.Finalized()) + if ((HasFlag(MovementGeneratorFlags.SpeedUpdatePending) && !owner.MoveSpline.Finalized()) || (_timer.Passed() && owner.MoveSpline.Finalized())) + { + RemoveFlag(MovementGeneratorFlags.Transitory); SetRandomLocation(owner); + } return true; } - public override void DoFinalize(Creature owner) + public override void DoDeactivate(Creature owner) { - owner.ClearUnitState(UnitState.Roaming); - owner.StopMoving(); - owner.SetWalk(false); + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } + + public override void DoFinalize(Creature owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + { + owner.ClearUnitState(UnitState.RoamingMove); + owner.StopMoving(); + + // TODO: Research if this modification is needed, which most likely isnt + owner.SetWalk(false); + } } void SetRandomLocation(Creature owner) @@ -87,14 +111,12 @@ namespace Game.Movement if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - _interrupt = true; + AddFlag(MovementGeneratorFlags.Interrupted); owner.StopMoving(); _path = null; return; } - owner.AddUnitState(UnitState.RoamingMove); - Position position = new(_reference); float distance = RandomHelper.FRand(0.0f, 1.0f) * _wanderDistance; float angle = RandomHelper.FRand(0.0f, 1.0f) * MathF.PI * 2.0f; @@ -115,6 +137,8 @@ namespace Game.Movement return; } + owner.AddUnitState(UnitState.RoamingMove); + MoveSplineInit init = new(owner); init.MovebyPath(_path.GetPath()); init.SetWalk(true); @@ -125,6 +149,8 @@ namespace Game.Movement owner.SignalFormationMovement(position); } + public override void UnitSpeedChanged() { AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Random; @@ -134,6 +160,5 @@ namespace Game.Movement TimeTracker _timer; Position _reference; float _wanderDistance; - bool _interrupt; } } diff --git a/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs b/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs index de84ce491..cb01510c8 100644 --- a/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs +++ b/Source/Game/Movement/Generators/SplineChainMovementGenerator.cs @@ -23,7 +23,7 @@ using System.Collections.Generic; namespace Game.Movement { - public class SplineChainMovementGenerator : IMovementGenerator + public class SplineChainMovementGenerator : MovementGenerator { public SplineChainMovementGenerator(uint id, List chain, bool walk = false) { @@ -31,117 +31,110 @@ namespace Game.Movement _chain = chain; _chainSize = (byte)chain.Count; _walk = walk; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } + public SplineChainMovementGenerator(SplineChainResumeInfo info) { _id = info.PointID; _chain = info.Chain; _chainSize = (byte)info.Chain.Count; _walk = info.IsWalkMode; - finished = info.SplineIndex >= info.Chain.Count; _nextIndex = info.SplineIndex; _nextFirstWP = info.PointIndex; _msToNext = info.TimeToNext; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + if (info.SplineIndex >= info.Chain.Count) + AddFlag(MovementGeneratorFlags.Finalized); + + BaseUnitState = UnitState.Roaming; } - uint SendPathSpline(Unit me, Span wp) + public override void Initialize(Unit owner) { - int numWp = wp.Length; - Cypher.Assert(numWp > 1, "Every path must have source & destination"); - MoveSplineInit init = new(me); - if (numWp > 2) - init.MovebyPath(wp.ToArray()); - else - init.MoveTo(wp[1], false, true); - init.SetWalk(_walk); - return (uint)init.Launch(); - } + RemoveFlag(MovementGeneratorFlags.InitializationPending); + AddFlag(MovementGeneratorFlags.Initialized); - void SendSplineFor(Unit me, int index, ref uint toNext) - { - Cypher.Assert(index < _chainSize); - Log.outDebug(LogFilter.Movement, "{0}: Sending spline for {1}.", me.GetGUID().ToString(), index); - - SplineChainLink thisLink = _chain[index]; - uint actualDuration = SendPathSpline(me, new Span(thisLink.Points.ToArray())); - if (actualDuration != thisLink.ExpectedDuration) + if (_chainSize == 0) { - Log.outDebug(LogFilter.Movement, "{0}: Sent spline for {1}, duration is {2} ms. Expected was {3} ms (delta {4} ms). Adjusting.", me.GetGUID().ToString(), index, actualDuration, thisLink.ExpectedDuration, actualDuration - thisLink.ExpectedDuration); - toNext = (uint)(actualDuration / thisLink.ExpectedDuration * toNext); - } - else - { - Log.outDebug(LogFilter.Movement, "{0}: Sent spline for {1}, duration is {2} ms.", me.GetGUID().ToString(), index, actualDuration); - } - } - - public void Initialize(Unit me) - { - if (_chainSize != 0) - { - if (_nextFirstWP != 0) // this is a resumed movegen that has to start with a partial spline - { - if (finished) - return; - SplineChainLink thisLink = _chain[_nextIndex]; - if (_nextFirstWP >= thisLink.Points.Count) - { - Log.outError(LogFilter.Movement, "{0}: Attempted to resume spline chain from invalid resume state ({1}, {2}).", me.GetGUID().ToString(), _nextIndex, _nextFirstWP); - _nextFirstWP = (byte)(thisLink.Points.Count - 1); - } - Span span = thisLink.Points.ToArray(); - SendPathSpline(me, span[(_nextFirstWP - 1)..]); - Log.outDebug(LogFilter.Movement, "{0}: Resumed spline chain generator from resume state.", me.GetGUID().ToString()); - ++_nextIndex; - if (_nextIndex >= _chainSize) - _msToNext = 0; - else if (_msToNext == 0) - _msToNext = 1; - _nextFirstWP = 0; - } - else - { - _msToNext = Math.Max(_chain[_nextIndex].TimeToNext, 1u); - SendSplineFor(me, _nextIndex, ref _msToNext); - ++_nextIndex; - if (_nextIndex >= _chainSize) - _msToNext = 0; - } - } - else - { - Log.outError(LogFilter.Movement, "SplineChainMovementGenerator.Initialize - empty spline chain passed for {0}.", me.GetGUID().ToString()); - } - } - - public void Finalize(Unit me) - { - if (!finished) + Log.outError(LogFilter.Movement, $"SplineChainMovementGenerator::Initialize: couldn't initialize generator, referenced spline is empty! ({owner.GetGUID()})"); return; + } - Creature cMe = me.ToCreature(); - if (cMe && cMe.IsAIEnabled) - cMe.GetAI().MovementInform(MovementGeneratorType.SplineChain, _id); + if (_nextFirstWP != 0) // this is a resumed movegen that has to start with a partial spline + { + + if (HasFlag(MovementGeneratorFlags.Finalized)) + return; + + SplineChainLink thisLink = _chain[_nextIndex]; + if (_nextFirstWP >= thisLink.Points.Count) + { + Log.outError(LogFilter.Movement, $"SplineChainMovementGenerator::Initialize: attempted to resume spline chain from invalid resume state, _nextFirstWP >= path size (_nextIndex: {_nextIndex}, _nextFirstWP: {_nextFirstWP}). ({owner.GetGUID()})"); + _nextFirstWP = (byte)(thisLink.Points.Count - 1); + } + + owner.AddUnitState(UnitState.RoamingMove); + Span partial = thisLink.Points.ToArray(); + SendPathSpline(owner, partial[(_nextFirstWP - 1)..]); + + Log.outDebug(LogFilter.Movement, $"SplineChainMovementGenerator::Initialize: resumed spline chain generator from resume state. ({owner.GetGUID()})"); + + ++_nextIndex; + if (_nextIndex >= _chainSize) + _msToNext = 0; + else if (_msToNext == 0) + _msToNext = 1; + _nextFirstWP = 0; + } + else + { + _msToNext = Math.Max(_chain[_nextIndex].TimeToNext, 1u); + SendSplineFor(owner, _nextIndex, ref _msToNext); + + ++_nextIndex; + if (_nextIndex >= _chainSize) + _msToNext = 0; + } } - public bool Update(Unit me, uint diff) + public override void Reset(Unit owner) { - if (finished) + RemoveFlag(MovementGeneratorFlags.Deactivated); + + owner.StopMoving(); + Initialize(owner); + } + + public override bool Update(Unit owner, uint diff) + { + if (owner == null || HasFlag(MovementGeneratorFlags.Finalized)) return false; // _msToNext being zero here means we're on the final spline if (_msToNext == 0) { - finished = me.MoveSpline.Finalized(); - return !finished; + if (owner.MoveSpline.Finalized()) + { + AddFlag(MovementGeneratorFlags.InformEnabled); + return false; + } + return true; } if (_msToNext <= diff) { // Send next spline - Log.outDebug(LogFilter.Movement, "{0}: Should send spline {1} ({2} ms late).", me.GetGUID().ToString(), _nextIndex, diff - _msToNext); + Log.outDebug(LogFilter.Movement, $"SplineChainMovementGenerator::Update: sending spline on index {_nextIndex} ({diff - _msToNext} ms late). ({owner.GetGUID()})"); _msToNext = Math.Max(_chain[_nextIndex].TimeToNext, 1u); - SendSplineFor(me, _nextIndex, ref _msToNext); + SendSplineFor(owner, _nextIndex, ref _msToNext); ++_nextIndex; if (_nextIndex >= _chainSize) { @@ -152,32 +145,87 @@ namespace Game.Movement } else _msToNext -= diff; + return true; } - SplineChainResumeInfo GetResumeInfo(Unit me) + public override void Deactivate(Unit owner) + { + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } + + public override void Finalize(Unit owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + + if (active) + owner.ClearUnitState(UnitState.RoamingMove); + + if (movementInform && HasFlag(MovementGeneratorFlags.InformEnabled)) + { + Creature ownerCreature = owner.ToCreature(); + if (ownerCreature != null && ownerCreature.IsAIEnabled) + ownerCreature.GetAI().MovementInform(MovementGeneratorType.SplineChain, _id); + } + } + + uint SendPathSpline(Unit owner, Span path) + { + int nodeCount = path.Length; + Cypher.Assert(nodeCount > 1, $"SplineChainMovementGenerator::SendPathSpline: Every path must have source & destination (size > 1)! ({owner.GetGUID()})"); + + MoveSplineInit init = new(owner); + if (nodeCount > 2) + init.MovebyPath(path.ToArray()); + else + init.MoveTo(path[1], false, true); + init.SetWalk(_walk); + return (uint)init.Launch(); + } + + void SendSplineFor(Unit owner, int index, ref uint duration) + { + Cypher.Assert(index < _chainSize, $"SplineChainMovementGenerator::SendSplineFor: referenced index ({index}) higher than path size ({_chainSize})!"); + Log.outDebug(LogFilter.Movement, $"SplineChainMovementGenerator::SendSplineFor: sending spline on index: {index}. ({owner.GetGUID()})"); + + SplineChainLink thisLink = _chain[index]; + uint actualDuration = SendPathSpline(owner, new Span(thisLink.Points.ToArray())); + if (actualDuration != thisLink.ExpectedDuration) + { + Log.outDebug(LogFilter.Movement, $"SplineChainMovementGenerator::SendSplineFor: sent spline on index: {index}, duration: {actualDuration} ms. Expected duration: {thisLink.ExpectedDuration} ms (delta {actualDuration - thisLink.ExpectedDuration} ms). Adjusting. ({owner.GetGUID()})"); + duration = (uint)((double)actualDuration / (double)thisLink.ExpectedDuration * duration); + } + else + { + Log.outDebug(LogFilter.Movement, $"SplineChainMovementGenerator::SendSplineFor: sent spline on index {index}, duration: {actualDuration} ms. ({owner.GetGUID()})"); + } + } + + SplineChainResumeInfo GetResumeInfo(Unit owner) { if (_nextIndex == 0) return new SplineChainResumeInfo(_id, _chain, _walk, 0, 0, _msToNext); - if (me.MoveSpline.Finalized()) + + if (owner.MoveSpline.Finalized()) { if (_nextIndex < _chainSize) return new SplineChainResumeInfo(_id, _chain, _walk, _nextIndex, 0, 1u); else return new SplineChainResumeInfo(); } - return new SplineChainResumeInfo(_id, _chain, _walk, (byte)(_nextIndex - 1), (byte)(me.MoveSpline.CurrentSplineIdx()), _msToNext); + + return new SplineChainResumeInfo(_id, _chain, _walk, (byte)(_nextIndex - 1), (byte)owner.MoveSpline.CurrentSplineIdx(), _msToNext); } - public void Reset(Unit owner) { } + public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.SplineChain; } - public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.SplineChain; } + public uint GetId() { return _id; } - uint _id; + uint _id; List _chain = new(); byte _chainSize; bool _walk; - bool finished; byte _nextIndex; byte _nextFirstWP; // only used for resuming uint _msToNext; diff --git a/Source/Game/Movement/Generators/WaypointMovement.cs b/Source/Game/Movement/Generators/WaypointMovement.cs index fc3407f2c..915bee971 100644 --- a/Source/Game/Movement/Generators/WaypointMovement.cs +++ b/Source/Game/Movement/Generators/WaypointMovement.cs @@ -30,60 +30,211 @@ namespace Game.Movement _pathId = pathId; _repeating = repeating; _loadedFromDB = true; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } public WaypointMovementGenerator(WaypointPath path, bool repeating = true) { _nextMoveTime = new TimeTrackerSmall(0); - _repeating = repeating; + _repeating = repeating; _path = path; + + Mode = MovementGeneratorMode.Default; + Priority = MovementGeneratorPriority.Normal; + Flags = MovementGeneratorFlags.InitializationPending; + BaseUnitState = UnitState.Roaming; } - public override void DoInitialize(Creature creature) + public override void Pause(uint timer = 0) { - _done = false; + if (timer != 0) + { + AddFlag(MovementGeneratorFlags.TimedPaused); + _nextMoveTime.Reset((int)timer); + RemoveFlag(MovementGeneratorFlags.Paused); + } + else + { + AddFlag(MovementGeneratorFlags.Paused); + _nextMoveTime.Reset(1); // Needed so that Update does not behave as if node was reached + RemoveFlag(MovementGeneratorFlags.TimedPaused); + } + } + + public override void Resume(uint overrideTimer = 0) + { + if (overrideTimer != 0) + _nextMoveTime.Reset((int)overrideTimer); + + if (_nextMoveTime.Passed()) + _nextMoveTime.Reset(1); // Needed so that Update does not behave as if node was reached + + RemoveFlag(MovementGeneratorFlags.Paused); + } + + public override bool GetResetPosition(Unit owner, out float x, out float y, out float z) + { + x = y = z = 0; + + // prevent a crash at empty waypoint path. + if (_path == null || _path.nodes.Empty()) + return false; + + Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator::GetResetPosition: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); + WaypointNode waypoint = _path.nodes.ElementAt(_currentNode); + + x = waypoint.x; + y = waypoint.y; + z = waypoint.z; + return true; + } + + public override void DoInitialize(Creature owner) + { + RemoveFlag(MovementGeneratorFlags.InitializationPending); if (_loadedFromDB) { if (_pathId == 0) - _pathId = creature.GetWaypointPath(); + _pathId = owner.GetWaypointPath(); _path = Global.WaypointMgr.GetPath(_pathId); } if (_path == null) { - // No path id found for entry - Log.outError(LogFilter.Sql, $"WaypointMovementGenerator.DoInitialize: creature {creature.GetName()} ({creature.GetGUID()} DB GUID: {creature.GetSpawnId()}) doesn't have waypoint path id: {_pathId}"); + Log.outError(LogFilter.Sql, $"WaypointMovementGenerator::DoInitialize: couldn't load path for creature ({owner.GetGUID()}) (_pathId: {_pathId})"); return; } + owner.StopMoving(); + _nextMoveTime.Reset(1000); // inform AI - if (creature.IsAIEnabled) - creature.GetAI().WaypointPathStarted(_path.id); + if (owner.IsAIEnabled) + owner.GetAI().WaypointPathStarted(_path.id); } - public override void DoFinalize(Creature creature) + public override void DoReset(Creature owner) { - creature.ClearUnitState(UnitState.Roaming | UnitState.RoamingMove); - creature.SetWalk(false); + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.Deactivated); + + owner.StopMoving(); + + if (!HasFlag(MovementGeneratorFlags.Finalized) && _nextMoveTime.Passed()) + _nextMoveTime.Reset(1); // Needed so that Update does not behave as if node was reached } - public override void DoReset(Creature creature) + public override bool DoUpdate(Creature owner, uint diff) { - if (!_done && _nextMoveTime.Passed() && CanMove(creature)) - StartMove(creature); - else if (_done) + if (!owner || !owner.IsAlive()) + return true; + + if (HasFlag(MovementGeneratorFlags.Finalized | MovementGeneratorFlags.Paused) || _path == null || _path.nodes.Empty()) + return true; + + if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting()) { - // mimic IdleMovementGenerator - if (!creature.IsStopped()) - creature.StopMoving(); + AddFlag(MovementGeneratorFlags.Interrupted); + owner.StopMoving(); + return true; + } + + if (HasFlag(MovementGeneratorFlags.Interrupted)) + { + /* + * relaunch only if + * - has a tiner? -> was it interrupted while not waiting aka moving? need to check both: + * -> has a timer - is it because its waiting to start next node? + * -> has a timer - is it because something set it while moving (like timed pause)? + * + * - doesnt have a timer? -> is movement valid? + * + * TODO: ((_nextMoveTime.Passed() && VALID_MOVEMENT) || (!_nextMoveTime.Passed() && !HasFlag(MOVEMENTGENERATOR_FLAG_INFORM_ENABLED))) + */ + if (HasFlag(MovementGeneratorFlags.Initialized) && (_nextMoveTime.Passed() || !HasFlag(MovementGeneratorFlags.InformEnabled))) + { + StartMove(owner, true); + return true; + } + + RemoveFlag(MovementGeneratorFlags.Interrupted); + } + + // if it's moving + if (!owner.MoveSpline.Finalized()) + { + // set home position at place (every MotionMaster::UpdateMotion) + if (owner.GetTransGUID().IsEmpty()) + owner.SetHomePosition(owner.GetPosition()); + + // relaunch movement if its speed has changed + if (HasFlag(MovementGeneratorFlags.SpeedUpdatePending)) + StartMove(owner, true); + } + else if (!_nextMoveTime.Passed()) // it's not moving, is there a timer? + { + if (UpdateTimer(diff)) + { + if (!HasFlag(MovementGeneratorFlags.Initialized)) // initial movement call + { + StartMove(owner); + return true; + } + else if (!HasFlag(MovementGeneratorFlags.InformEnabled)) // timer set before node was reached, resume now + { + StartMove(owner, true); + return true; + } + } + else + return true; // keep waiting + } + else // not moving, no timer + { + if (HasFlag(MovementGeneratorFlags.Initialized) && !HasFlag(MovementGeneratorFlags.InformEnabled)) + { + OnArrived(owner); // hooks and wait timer reset (if necessary) + AddFlag(MovementGeneratorFlags.InformEnabled); // signals to future StartMove that it reached a node + } + + if (_nextMoveTime.Passed()) // OnArrived might have set a timer + StartMove(owner); // check path status, get next point and move if necessary & can + } + + return true; + } + + public override void DoDeactivate(Creature owner) + { + AddFlag(MovementGeneratorFlags.Deactivated); + owner.ClearUnitState(UnitState.RoamingMove); + } + + public override void DoFinalize(Creature owner, bool active, bool movementInform) + { + AddFlag(MovementGeneratorFlags.Finalized); + if (active) + { + owner.ClearUnitState(UnitState.RoamingMove); + + // TODO: Research if this modification is needed, which most likely isnt + owner.SetWalk(false); } } - void OnArrived(Creature creature) + void MovementInform(Creature owner) + { + if (owner.IsAIEnabled) + owner.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); + } + + void OnArrived(Creature owner) { if (_path == null || _path.nodes.Empty()) return; @@ -92,103 +243,106 @@ namespace Game.Movement WaypointNode waypoint = _path.nodes.ElementAt((int)_currentNode); if (waypoint.delay != 0) { - creature.ClearUnitState(UnitState.RoamingMove); + owner.ClearUnitState(UnitState.RoamingMove); _nextMoveTime.Reset((int)waypoint.delay); } if (waypoint.eventId != 0 && RandomHelper.URand(0, 99) < waypoint.eventChance) { - 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); + Log.outDebug(LogFilter.MapsScript, $"Creature movement start script {waypoint.eventId} at point {_currentNode} for {owner.GetGUID()}."); + owner.ClearUnitState(UnitState.RoamingMove); + owner.GetMap().ScriptsStart(ScriptsType.Waypoint, waypoint.eventId, owner, null); } // inform AI - if (creature.IsAIEnabled) + if (owner.IsAIEnabled) { - creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); - creature.GetAI().WaypointReached(waypoint.id, _path.id); + owner.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); + owner.GetAI().WaypointReached(waypoint.id, _path.id); } - creature.UpdateCurrentWaypointInfo(waypoint.id, _path.id); + owner.UpdateCurrentWaypointInfo(waypoint.id, _path.id); } - void StartMove(Creature creature, bool relaunch = false) + void StartMove(Creature owner, bool relaunch = false) { // sanity checks - if (creature == null || !creature.IsAlive() || _done || _path == null || _path.nodes.Empty() || (relaunch && _isArrivalDone)) + if (owner == null || !owner.IsAlive() || HasFlag(MovementGeneratorFlags.Finalized) || _path == null || _path.nodes.Empty() || (relaunch && (HasFlag(MovementGeneratorFlags.InformEnabled) || !HasFlag(MovementGeneratorFlags.Initialized)))) return; - if (!relaunch) // on relaunch, can avoid this since its only called on valid movement + if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting() || (owner.IsFormationLeader() && !owner.IsFormationLeaderMoveAllowed())) // if cannot move OR cannot move because of formation { - if (!CanMove(creature) || (creature.IsFormationLeader() && !creature.IsFormationLeaderMoveAllowed())) // if cannot move OR cannot move because of formation - { - _nextMoveTime.Reset(1000); // delay 1s - return; - } + _nextMoveTime.Reset(1000); // delay 1s + return; } - bool transportPath = creature.GetTransport() != null; + bool transportPath = !owner.GetTransGUID().IsEmpty(); - if (_isArrivalDone) + if (HasFlag(MovementGeneratorFlags.InformEnabled) && HasFlag(MovementGeneratorFlags.Initialized)) { - Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); - WaypointNode lastWaypoint = _path.nodes.ElementAt(_currentNode); - if ((_currentNode == _path.nodes.Count - 1) && !_repeating) // If that's our last waypoint + if (ComputeNextNode()) { - float x = lastWaypoint.x; - float y = lastWaypoint.y; - float z = lastWaypoint.z; - float o = creature.GetOrientation(); + Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); + // inform AI + if (owner.IsAIEnabled) + owner.GetAI().WaypointStarted(_path.nodes[_currentNode].id, _path.id); + } + else + { + WaypointNode currentWaypoint = _path.nodes[_currentNode]; + float x = currentWaypoint.x; + float y = currentWaypoint.y; + float z = currentWaypoint.z; + float o = owner.GetOrientation(); if (!transportPath) - creature.SetHomePosition(x, y, z, o); + owner.SetHomePosition(x, y, z, o); else { - Transport trans = creature.GetTransport(); + Transport trans = owner.GetTransport(); if (trans) { o -= trans.GetOrientation(); - creature.SetTransportHomePosition(x, y, z, o); + owner.SetTransportHomePosition(x, y, z, o); trans.CalculatePassengerPosition(ref x, ref y, ref z, ref o); - creature.SetHomePosition(x, y, z, o); + owner.SetHomePosition(x, y, z, o); } - else - transportPath = false; // else if (vehicle) - this should never happen, vehicle offsets are const } - _done = true; - creature.UpdateCurrentWaypointInfo(0, 0); + + AddFlag(MovementGeneratorFlags.Finalized); + owner.UpdateCurrentWaypointInfo(0, 0); // inform AI - if (creature.IsAIEnabled) - creature.GetAI().WaypointPathEnded(lastWaypoint.id, _path.id); + if (owner.IsAIEnabled) + owner.GetAI().WaypointPathEnded(currentWaypoint.id, _path.id); return; } - - _currentNode = (_currentNode + 1) % _path.nodes.Count; + } + else if (!HasFlag(MovementGeneratorFlags.Initialized)) + { + AddFlag(MovementGeneratorFlags.Initialized); // inform AI - if (creature.IsAIEnabled) - creature.GetAI().WaypointStarted(lastWaypoint.id, _path.id); + if (owner.IsAIEnabled) + owner.GetAI().WaypointStarted(_path.nodes[_currentNode].id, _path.id); } Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.StartMove: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); WaypointNode waypoint = _path.nodes[_currentNode]; Position formationDest = new(waypoint.x, waypoint.y, waypoint.z, (waypoint.orientation != 0 && waypoint.delay != 0) ? waypoint.orientation : 0.0f); - _isArrivalDone = false; - _recalculateSpeed = false; + RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.InformEnabled | MovementGeneratorFlags.TimedPaused); - creature.AddUnitState(UnitState.RoamingMove); + owner.AddUnitState(UnitState.RoamingMove); - MoveSplineInit init = new(creature); + MoveSplineInit init = new(owner); //! If creature is on transport, we assume waypoints set in DB are already transport offsets if (transportPath) { init.DisableTransportPathTransformations(); - ITransport trans = creature.GetDirectTransport(); + ITransport trans = owner.GetDirectTransport(); if (trans != null) { float orientation = formationDest.GetOrientation(); @@ -224,113 +378,37 @@ namespace Game.Movement init.Launch(); // inform formation - creature.SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation != 0 && waypoint.delay != 0)); + owner.SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation != 0 && waypoint.delay != 0)); } - public override bool DoUpdate(Creature creature, uint diff) + bool ComputeNextNode() { - if (!creature || !creature.IsAlive()) - return true; - - if (_done || _path == null || _path.nodes.Empty()) - return true; - - if (_stalled || creature.HasUnitState(UnitState.NotMove) || creature.IsMovementPreventedByCasting()) - { - creature.StopMoving(); - return true; - } - - // if it's moving - if (!creature.MoveSpline.Finalized()) - { - // set home position at place (every MotionMaster::UpdateMotion) - if (creature.GetTransGUID().IsEmpty()) - creature.SetHomePosition(creature.GetPosition()); - - // relaunch movement if its speed has changed - if (_recalculateSpeed) - StartMove(creature, true); - } - else - { - // check if there is a wait time for the next movement - if (!_nextMoveTime.Passed()) - { - // update timer since it's not moving - _nextMoveTime.Update((int)diff); - if (_nextMoveTime.Passed()) - { - _nextMoveTime.Reset(0); - StartMove(creature); // check path status, get next point and move if necessary & can - } - } - else // if it's not moving and there is no timer, assume node is reached - { - OnArrived(creature); // hooks and wait timer reset (if necessary) - _isArrivalDone = true; // signals to future StartMove that it reached a node - - if (_nextMoveTime.Passed()) - StartMove(creature); // check path status, get next point and move if necessary & can - } - } - - return true; - } - - void MovementInform(Creature creature) - { - if (creature.IsAIEnabled) - creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode); - } - - public 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()) + if ((_currentNode == _path.nodes.Count - 1) && !_repeating) return false; - Cypher.Assert(_currentNode < _path.nodes.Count, $"WaypointMovementGenerator.GetResetPos: tried to reference a node id ({_currentNode}) which is not included in path ({_path.id})"); - WaypointNode waypoint = _path.nodes.ElementAt(_currentNode); - - x = waypoint.x; - y = waypoint.y; - z = waypoint.z; + _currentNode = (_currentNode + 1) % _path.nodes.Count; return true; } - public void Pause(uint timer = 0) + bool UpdateTimer(uint diff) { - _stalled = timer == 0; - _nextMoveTime.Reset(timer != 0 ? (int)timer : 1); - } - - public void Resume(uint overrideTimer = 0) - { - _stalled = false; - if (overrideTimer != 0) - _nextMoveTime.Reset((int)overrideTimer); - } - - static bool CanMove(Creature creature) - { - return !creature.HasUnitState(UnitState.NotMove) && !creature.IsMovementPreventedByCasting(); + _nextMoveTime.Update((int)diff); + if (_nextMoveTime.Passed()) + { + _nextMoveTime.Reset(0); + return true; + } + return false; } public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Waypoint; } - public void UnitSpeedChanged() { _recalculateSpeed = true; } + public override void UnitSpeedChanged() { AddFlag(MovementGeneratorFlags.SpeedUpdatePending); } TimeTrackerSmall _nextMoveTime; - bool _recalculateSpeed; - bool _isArrivalDone; uint _pathId; bool _repeating; bool _loadedFromDB; - bool _stalled; - bool _done; WaypointPath _path; int _currentNode; diff --git a/Source/Game/Movement/MotionMaster.cs b/Source/Game/Movement/MotionMaster.cs index 226ab388d..0e4e30894 100644 --- a/Source/Game/Movement/MotionMaster.cs +++ b/Source/Game/Movement/MotionMaster.cs @@ -22,115 +22,151 @@ using Game.DataStorage; using Game.Entities; using System; using System.Collections.Generic; +using System.Linq; namespace Game.Movement { + class MovementGeneratorComparator : IComparer + { + public int Compare(MovementGenerator a, MovementGenerator b) + { + if (a.Mode > b.Mode) + return 1; + else if (a.Mode == b.Mode) + return a.Priority > b.Priority ? 1 : -1; + + return 0; + } + } + + public struct MovementGeneratorInformation + { + public MovementGeneratorType Type; + public ObjectGuid TargetGUID; + public string TargetName; + + public MovementGeneratorInformation(MovementGeneratorType type, ObjectGuid targetGUID, string targetName = "") + { + Type = type; + TargetGUID = targetGUID; + TargetName = targetName; + } + } + + class MotionMasterDelayedAction + { + public Action Action; + public MotionMasterDelayedActionType Type; + + public MotionMasterDelayedAction(Action action, MotionMasterDelayedActionType type) + { + Action = action; + Type = type; + } + + public void Resolve() { Action(); } + } + public class MotionMaster { public const double gravity = 19.29110527038574; public const float SPEED_CHARGE = 42.0f; - IdleMovementGenerator staticIdleMovement = new(); + static IdleMovementGenerator staticIdleMovement = new(); + static uint splineId; - public MotionMaster(Unit me) + Unit _owner { get; } + MovementGenerator _defaultGenerator { get; set; } + SortedSet _generators { get; } = new(new MovementGeneratorComparator()); + MultiMap _baseUnitStatesMap { get; } = new(); + Queue _delayedActions { get; } = new(); + MotionMasterFlags _flags { get; set; } + + public MotionMaster(Unit unit) { - _owner = me; - _top = -1; - _cleanFlag = MotionMasterCleanFlag.None; - - for (byte i = 0; i < (int)MovementSlot.Max; ++i) - { - _slot[i] = null; - _initialize[i] = true; - } - } - - public IMovementGenerator Top() - { - Cypher.Assert(!Empty()); - return _slot[_top]; + _owner = unit; } public void Initialize() { - while (!Empty()) + if (HasFlag(MotionMasterFlags.Update)) { - IMovementGenerator curr = Top(); - Pop(); - if (curr != null) - DirectDelete(curr); - } - - InitDefault(); - } - - public void InitDefault() - { - if (_owner.IsTypeId(TypeId.Unit)) - { - IMovementGenerator movement = AISelector.SelectMovementAI(_owner); - StartMovement(movement ?? staticIdleMovement, MovementSlot.Idle); - } - else - StartMovement(staticIdleMovement, MovementSlot.Idle); - } - - public virtual void UpdateMotion(uint diff) - { - if (!_owner) + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Initialize(), MotionMasterDelayedActionType.Initialize)); return; - - Cypher.Assert(!Empty()); - - _cleanFlag |= MotionMasterCleanFlag.Update; - if (!Top().Update(_owner, diff)) - { - _cleanFlag &= ~MotionMasterCleanFlag.Update; - MovementExpired(); } - else - _cleanFlag &= ~MotionMasterCleanFlag.Update; - if (!_expireList.Empty()) - ClearExpireList(); + DirectInitialize(); } - public void Clear(bool reset = true) + public void InitializeDefault() { - if (Convert.ToBoolean(_cleanFlag & MotionMasterCleanFlag.Update)) - { - if (reset) - _cleanFlag |= MotionMasterCleanFlag.Reset; - else - _cleanFlag &= ~MotionMasterCleanFlag.Reset; - DelayedClean(); - } - else - DirectClean(reset); + Add(AI.AISelector.SelectMovementGenerator(_owner), MovementSlot.Default); } - public void Clear(MovementSlot slot) + public bool Empty() { - if (Empty() || IsInvalidMovementSlot(slot)) - return; - - if (_cleanFlag.HasAnyFlag(MotionMasterCleanFlag.Update)) - DelayedClean(slot); - else - DirectClean(slot); + return _defaultGenerator == null && _generators.Empty(); } - public void MovementExpired(bool reset = true) + int Size() { - if (Convert.ToBoolean(_cleanFlag & MotionMasterCleanFlag.Update)) + return _defaultGenerator != null ? 1 : 0 + _generators.Count; + } + + public List GetMovementGeneratorsInformation() + { + List list = new(); + + if (_defaultGenerator != null) + list.Add(new MovementGeneratorInformation(_defaultGenerator.GetMovementGeneratorType(), ObjectGuid.Empty, "")); + + foreach (var movement in _generators) { - if (reset) - _cleanFlag |= MotionMasterCleanFlag.Reset; - else - _cleanFlag &= ~MotionMasterCleanFlag.Reset; - DelayedExpire(); + MovementGeneratorType type = movement.GetMovementGeneratorType(); + switch (type) + { + case MovementGeneratorType.Chase: + case MovementGeneratorType.Follow: + var followInformation = movement as FollowMovementGenerator; + if (followInformation != null) + { + Unit target = followInformation.GetTarget(); + if (target != null) + list.Add(new MovementGeneratorInformation(type, target.GetGUID(), target.GetName())); + else + list.Add(new MovementGeneratorInformation(type, ObjectGuid.Empty)); + } + else + list.Add(new MovementGeneratorInformation(type, ObjectGuid.Empty)); + break; + default: + list.Add(new MovementGeneratorInformation(type, ObjectGuid.Empty)); + break; + } } - else - DirectExpire(reset); + + return list; + } + + public MovementSlot GetCurrentSlot() + { + if (!_generators.Empty()) + return MovementSlot.Active; + + if (_defaultGenerator != null) + return MovementSlot.Default; + + return MovementSlot.Max; + } + + public MovementGenerator GetCurrentMovementGenerator() + { + if (!_generators.Empty()) + return _generators.FirstOrDefault(); + + if (_defaultGenerator != null) + return _defaultGenerator; + + return null; } public MovementGeneratorType GetCurrentMovementGeneratorType() @@ -138,33 +174,290 @@ namespace Game.Movement if (Empty()) return MovementGeneratorType.Max; - IMovementGenerator movement = Top(); + MovementGenerator movement = GetCurrentMovementGenerator(); if (movement == null) return MovementGeneratorType.Max; - return Top().GetMovementGeneratorType(); + return movement.GetMovementGeneratorType(); } - public MovementGeneratorType GetMotionSlotType(MovementSlot slot) + public MovementGeneratorType GetCurrentMovementGeneratorType(MovementSlot slot) { - if (Empty() || IsInvalidMovementSlot(slot) || _slot[(int)slot] == null) + if (Empty() || IsInvalidMovementSlot(slot)) return MovementGeneratorType.Max; - return _slot[(int)slot].GetMovementGeneratorType(); + if (slot == MovementSlot.Active && !_generators.Empty()) + return _generators.FirstOrDefault().GetMovementGeneratorType(); + + if (slot == MovementSlot.Default && _defaultGenerator != null) + return _defaultGenerator.GetMovementGeneratorType(); + + return MovementGeneratorType.Max; } - public IMovementGenerator GetMotionSlot(MovementSlot slot) + public MovementGenerator GetCurrentMovementGenerator(MovementSlot slot) { - if (Empty() || IsInvalidMovementSlot(slot) || _slot[(int)slot] == null) + if (Empty() || IsInvalidMovementSlot(slot)) return null; - return _slot[(int)slot]; + if (slot == MovementSlot.Active && !_generators.Empty()) + return _generators.FirstOrDefault(); + + if (slot == MovementSlot.Default && _defaultGenerator != null) + return _defaultGenerator; + + return null; } - public IMovementGenerator GetMotionSlot(int slot) + public MovementGenerator GetMovementGenerator(Func filter, MovementSlot slot = MovementSlot.Active) { - Cypher.Assert(slot >= 0); - return _slot[slot]; + + if (Empty() || IsInvalidMovementSlot(slot)) + return null; + + MovementGenerator movement = null; + switch (slot) + { + case MovementSlot.Default: + if (_defaultGenerator != null && filter(_defaultGenerator)) + movement = _defaultGenerator; + break; + case MovementSlot.Active: + if (!_generators.Empty()) + { + var itr = _generators.FirstOrDefault(filter); + if (itr != null) + movement = itr; + } + break; + default: + break; + } + + return movement; + } + + public bool HasMovementGenerator(Func filter, MovementSlot slot = MovementSlot.Active) + { + + if (Empty() || IsInvalidMovementSlot(slot)) + return false; + + bool value = false; + switch (slot) + { + case MovementSlot.Default: + if (_defaultGenerator != null && filter(_defaultGenerator)) + value = true; + break; + case MovementSlot.Active: + if (!_generators.Empty()) + { + var itr = _generators.FirstOrDefault(filter); + value = itr != null; + } + break; + default: + break; + } + + return value; + } + + public void Update(uint diff) + { + if (!_owner) + return; + + Cypher.Assert(!Empty(), $"MotionMaster:Update: update called without Initializing! ({_owner.GetGUID()})"); + + AddFlag(MotionMasterFlags.Update); + + MovementGenerator top = GetCurrentMovementGenerator(); + if (HasFlag(MotionMasterFlags.StaticInitializationPending) && IsStatic(top)) + { + RemoveFlag(MotionMasterFlags.StaticInitializationPending); + top.Initialize(_owner); + } + + if (top.HasFlag(MovementGeneratorFlags.InitializationPending)) + top.Initialize(_owner); + if (top.HasFlag(MovementGeneratorFlags.Deactivated)) + top.Reset(_owner); + + Cypher.Assert(!top.HasFlag(MovementGeneratorFlags.InitializationPending | MovementGeneratorFlags.Deactivated), $"MotionMaster:Update: update called on an uninitialized top! ({_owner.GetGUID()}) (type: {top.GetMovementGeneratorType()}, flags: {top.Flags})"); + + if (!top.Update(_owner, diff)) + { + Cypher.Assert(top == GetCurrentMovementGenerator(), $"MotionMaster::Update: top was modified while updating! ({_owner.GetGUID()})"); + + // Since all the actions that modify any slot are delayed, this movement is guaranteed to be top + Pop(true, true); // Natural, and only, call to MovementInform + } + + RemoveFlag(MotionMasterFlags.Update); + + while (_delayedActions.Count != 0) + { + _delayedActions.Peek().Resolve(); + _delayedActions.Dequeue(); + } + } + + void Add(MovementGenerator movement, MovementSlot slot = MovementSlot.Active) + { + if (movement == null) + return; + + if (IsInvalidMovementSlot(slot)) + return; + + if (HasFlag(MotionMasterFlags.Update)) + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Add(movement, slot), MotionMasterDelayedActionType.Add)); + else + DirectAdd(movement, slot); + } + + public void Remove(MovementGenerator movement, MovementSlot slot = MovementSlot.Active) + { + if (movement == null || IsInvalidMovementSlot(slot)) + return; + + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Remove(movement, slot), MotionMasterDelayedActionType.Remove)); + return; + } + + if (Empty()) + return; + + switch (slot) + { + case MovementSlot.Default: + if (_defaultGenerator != null && _defaultGenerator == movement) + DirectClearDefault(); + break; + case MovementSlot.Active: + if (!_generators.Empty()) + { + if (_generators.Contains(movement)) + { + bool top = GetCurrentMovementGenerator() == movement; + _generators.Remove(movement); + Delete(movement, top, false); + } + } + break; + default: + break; + } + } + + public void Remove(MovementGeneratorType type, MovementSlot slot = MovementSlot.Active) + { + if (IsInvalidMovementGeneratorType(type) || IsInvalidMovementSlot(slot)) + return; + + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Remove(type, slot), MotionMasterDelayedActionType.RemoveType)); + return; + } + + if (Empty()) + return; + + switch (slot) + { + case MovementSlot.Default: + if (_defaultGenerator != null && _defaultGenerator.GetMovementGeneratorType() == type) + DirectClearDefault(); + break; + case MovementSlot.Active: + if (!_generators.Empty()) + { + var itr = _generators.FirstOrDefault(a => a.GetMovementGeneratorType() == type); + if (itr != null) + { + MovementGenerator pointer = itr; + bool top = GetCurrentMovementGenerator() == pointer; + _generators.Remove(pointer); + Delete(pointer, top, false); + } + } + break; + default: + break; + } + } + + public void Clear() + { + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Clear(), MotionMasterDelayedActionType.Clear)); + return; + } + + if (!Empty()) + DirectClear(); + } + + public void Clear(MovementSlot slot) + { + if (IsInvalidMovementSlot(slot)) + return; + + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Clear(slot), MotionMasterDelayedActionType.ClearSlot)); + return; + } + + if (Empty()) + return; + + switch (slot) + { + case MovementSlot.Default: + DirectClearDefault(); + break; + case MovementSlot.Active: + DirectClear(); + break; + default: + break; + } + } + + public void Clear(MovementGeneratorMode mode) + { + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Clear(mode), MotionMasterDelayedActionType.ClearMode)); + return; + } + + if (Empty()) + return; + + DirectClear(a => a.Mode == mode); + } + + public void Clear(MovementGeneratorPriority priority) + { + + if (HasFlag(MotionMasterFlags.Update)) + { + _delayedActions.Enqueue(new MotionMasterDelayedAction(() => Clear(priority), MotionMasterDelayedActionType.ClearPriority)); + return; + } + + if (Empty()) + return; + + DirectClear(a => a.Priority == priority); } public void PropagateSpeedChange() @@ -172,7 +465,7 @@ namespace Game.Movement if (Empty()) return; - IMovementGenerator movement = Top(); + MovementGenerator movement = GetCurrentMovementGenerator(); if (movement == null) return; @@ -196,8 +489,7 @@ namespace Game.Movement public void MoveIdle() { - if (Empty() || !IsStatic(Top())) - StartMovement(staticIdleMovement, MovementSlot.Idle); + Add(GetIdleMovementGenerator(), MovementSlot.Default); } public void MoveTargetedHome() @@ -209,48 +501,49 @@ namespace Game.Movement return; } - Clear(false); + Clear(); Unit target = owner.GetCharmerOrOwner(); if (target == null) - StartMovement(new HomeMovementGenerator(), MovementSlot.Active); + Add(new HomeMovementGenerator()); else - StartMovement(new FollowMovementGenerator(target, SharedConst.PetFollowDist, new ChaseAngle(SharedConst.PetFollowAngle)), MovementSlot.Active); + Add(new FollowMovementGenerator(target, SharedConst.PetFollowDist, new ChaseAngle(SharedConst.PetFollowAngle))); } public void MoveRandom(float spawndist = 0.0f) { if (_owner.IsTypeId(TypeId.Unit)) - StartMovement(new RandomMovementGenerator(spawndist), MovementSlot.Idle); + Add(new RandomMovementGenerator(spawndist)); } - public void MoveFollow(Unit target, float dist, float angle = 0.0f, MovementSlot slot = MovementSlot.Idle) { MoveFollow(target, dist, new ChaseAngle(angle), slot); } + public void MoveFollow(Unit target, float dist, float angle = 0.0f, MovementSlot slot = MovementSlot.Active) { MoveFollow(target, dist, new ChaseAngle(angle), slot); } - public void MoveFollow(Unit target, float dist, ChaseAngle angle, MovementSlot slot = MovementSlot.Idle) + public void MoveFollow(Unit target, float dist, ChaseAngle angle, MovementSlot slot = MovementSlot.Active) { - // ignore movement request if target not exist + // Ignore movement request if target not exist if (!target || target == _owner) return; - StartMovement(new FollowMovementGenerator(target, dist, angle), slot); + Add(new FollowMovementGenerator(target, dist, angle), slot); } public void MoveChase(Unit target, float dist, float angle = 0.0f) { MoveChase(target, new ChaseRange(dist), new ChaseAngle(angle)); } public void MoveChase(Unit target, ChaseRange? dist = null, ChaseAngle? angle = null) { + // Ignore movement request if target not exist if (!target || target == _owner) return; - StartMovement(new ChaseMovementGenerator(target, dist, angle), MovementSlot.Active); + Add(new ChaseMovementGenerator(target, dist, angle)); } public void MoveConfused() { if (_owner.IsTypeId(TypeId.Player)) - StartMovement(new ConfusedGenerator(), MovementSlot.Controlled); + Add(new ConfusedGenerator()); else - StartMovement(new ConfusedGenerator(), MovementSlot.Controlled); + Add(new ConfusedGenerator()); } public void MoveFleeing(Unit enemy, uint time) @@ -261,12 +554,12 @@ namespace Game.Movement if (_owner.IsCreature()) { if (time != 0) - StartMovement(new TimedFleeingGenerator(enemy.GetGUID(), time), MovementSlot.Controlled); + Add(new TimedFleeingGenerator(enemy.GetGUID(), time)); else - StartMovement(new FleeingGenerator(enemy.GetGUID()), MovementSlot.Controlled); + Add(new FleeingGenerator(enemy.GetGUID())); } else - StartMovement(new FleeingGenerator(enemy.GetGUID()), MovementSlot.Controlled); + Add(new FleeingGenerator(enemy.GetGUID())); } public void MovePoint(uint id, Position pos, bool generatePath = true, float? finalOrient = null) @@ -277,9 +570,9 @@ namespace Game.Movement public void MovePoint(uint id, float x, float y, float z, bool generatePath = true, float? finalOrient = null) { if (_owner.IsTypeId(TypeId.Player)) - StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, 0.0f, null, null, finalOrient), MovementSlot.Active); + Add(new PointMovementGenerator(id, x, y, z, generatePath, 0.0f, finalOrient)); else - StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, 0.0f, null, null, finalOrient), MovementSlot.Active); + Add(new PointMovementGenerator(id, x, y, z, generatePath, 0.0f, finalOrient)); } public void MoveCloserAndStop(uint id, Unit target, float distance) @@ -294,11 +587,11 @@ namespace Game.Movement } else { - // we are already close enough. We just need to turn toward the target without changing position. + // We are already close enough. We just need to turn toward the target without changing position. MoveSplineInit init = new(_owner); init.MoveTo(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ()); init.SetFacing(target); - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id), MovementSlot.Active); + Add(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id)); } } @@ -307,7 +600,7 @@ namespace Game.Movement MoveSplineInit init = new(_owner); init.MoveTo(pos); init.SetAnimation(AnimType.ToGround); - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id), MovementSlot.Active); + Add(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id)); } public void MoveTakeoff(uint id, Position pos) @@ -316,18 +609,30 @@ namespace Game.Movement init.MoveTo(pos); init.SetAnimation(AnimType.ToFly); - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id), MovementSlot.Active); + Add(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id)); } public void MoveCharge(float x, float y, float z, float speed = SPEED_CHARGE, uint id = EventId.Charge, bool generatePath = false, Unit target = null, SpellEffectExtraData spellEffectExtraData = null) { + /* if (_slot[(int)MovementSlot.Controlled] != null && _slot[(int)MovementSlot.Controlled].GetMovementGeneratorType() != MovementGeneratorType.Distract) return; + */ if (_owner.IsTypeId(TypeId.Player)) - StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, speed, target, spellEffectExtraData), MovementSlot.Controlled); + { + PointMovementGenerator movement = new PointMovementGenerator(id, x, y, z, generatePath, speed, null, target, spellEffectExtraData); + movement.Priority = MovementGeneratorPriority.Highest; + movement.BaseUnitState = UnitState.Charging; + Add(movement); + } else - StartMovement(new PointMovementGenerator(id, x, y, z, generatePath, speed, target, spellEffectExtraData), MovementSlot.Controlled); + { + PointMovementGenerator movement = new PointMovementGenerator(id, x, y, z, generatePath, speed, null, target, spellEffectExtraData); + movement.Priority = MovementGeneratorPriority.Highest; + movement.BaseUnitState = UnitState.Charging; + Add(movement); + } } public void MoveCharge(PathGenerator path, float speed = SPEED_CHARGE, Unit target = null, SpellEffectExtraData spellEffectExtraData = null) @@ -349,7 +654,7 @@ namespace Game.Movement public void MoveKnockbackFrom(float srcX, float srcY, float speedXY, float speedZ, SpellEffectExtraData spellEffectExtraData = null) { - //this function may make players fall below map + //This function may make players fall below map if (_owner.IsTypeId(TypeId.Player)) return; @@ -371,12 +676,14 @@ namespace Game.Movement if (spellEffectExtraData != null) init.SetSpellEffectExtraData(spellEffectExtraData); - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, 0), MovementSlot.Controlled); + GenericMovementGenerator movement = new GenericMovementGenerator(init, MovementGeneratorType.Effect, 0); + movement.Priority = MovementGeneratorPriority.Highest; + Add(movement); } public void MoveJumpTo(float angle, float speedXY, float speedZ) { - //this function may make players fall below map + //This function may make players fall below map if (_owner.IsTypeId(TypeId.Player)) return; @@ -393,8 +700,7 @@ namespace Game.Movement MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), speedXY, speedZ, id, hasOrientation, arrivalCast, spellEffectExtraData); } - public void MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint id = EventId.Jump, bool hasOrientation = false, - JumpArrivalCastArgs arrivalCast = null, SpellEffectExtraData spellEffectExtraData = null) + public void MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint id = EventId.Jump, bool hasOrientation = false, JumpArrivalCastArgs arrivalCast = null, SpellEffectExtraData spellEffectExtraData = null) { Log.outDebug(LogFilter.Server, "Unit ({0}) jump to point (X: {1} Y: {2} Z: {3})", _owner.GetGUID().ToString(), x, y, z); if (speedXY < 0.01f) @@ -420,7 +726,10 @@ namespace Game.Movement arrivalSpellTargetGuid = arrivalCast.Target; } - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id, arrivalSpellId, arrivalSpellTargetGuid), MovementSlot.Controlled); + GenericMovementGenerator movement = new GenericMovementGenerator(init, MovementGeneratorType.Effect, id, arrivalSpellId, arrivalSpellTargetGuid); + movement.Priority = MovementGeneratorPriority.Highest; + movement.BaseUnitState = UnitState.Jumping; + Add(movement); } public void MoveCirclePath(float x, float y, float z, float radius, bool clockwise, byte stepCount) @@ -457,7 +766,7 @@ namespace Game.Movement init.SetCyclic(); } - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, 0), MovementSlot.Active); + Add(new GenericMovementGenerator(init, MovementGeneratorType.Effect, 0)); } void MoveSmoothPath(uint pointId, Vector3[] pathPoints, int pathSize, bool walk = false, bool fly = false) @@ -474,9 +783,9 @@ namespace Game.Movement init.SetWalk(walk); // This code is not correct - // GenericMovementGenerator does not affect UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE + // GenericMovementGenerator does not affect UNIT_STATE_ROAMING_MOVE // need to call PointMovementGenerator with various pointIds - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, pointId), MovementSlot.Active); + Add(new GenericMovementGenerator(init, MovementGeneratorType.Effect, pointId)); } public void MoveAlongSplineChain(uint pointId, uint dbChainId, bool walk) @@ -498,7 +807,7 @@ namespace Game.Movement void MoveAlongSplineChain(uint pointId, List chain, bool walk) { - StartMovement(new SplineChainMovementGenerator(pointId, chain, walk), MovementSlot.Active); + Add(new SplineChainMovementGenerator(pointId, chain, walk)); } void ResumeSplineChain(SplineChainResumeInfo info) @@ -509,12 +818,12 @@ namespace Game.Movement return; } - StartMovement(new SplineChainMovementGenerator(info), MovementSlot.Active); + Add(new SplineChainMovementGenerator(info)); } public void MoveFall(uint id = 0) { - // use larger distance for vmap height search than in most other cases + // Use larger distance for vmap height search than in most other cases float tz = _owner.GetMapHeight(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), true, MapConst.MaxFallDistance); if (tz <= MapConst.InvalidHeight) return; @@ -525,7 +834,7 @@ namespace Game.Movement _owner.SetFall(true); - // don't run spline movement for players + // Don't run spline movement for players if (_owner.IsTypeId(TypeId.Player)) { _owner.ToPlayer().SetFallInformation(0, _owner.GetPositionZ()); @@ -535,7 +844,10 @@ namespace Game.Movement MoveSplineInit init = new(_owner); init.MoveTo(_owner.GetPositionX(), _owner.GetPositionY(), tz + _owner.GetHoverOffset(), false); init.SetFall(); - StartMovement(new GenericMovementGenerator(init, MovementGeneratorType.Effect, id), MovementSlot.Controlled); + + GenericMovementGenerator movement = new GenericMovementGenerator(init, MovementGeneratorType.Effect, id); + movement.Priority = MovementGeneratorPriority.Highest; + Add(movement); } public void MoveSeekAssistance(float x, float y, float z) @@ -545,7 +857,7 @@ namespace Game.Movement _owner.AttackStop(); _owner.CastStop(); _owner.ToCreature().SetReactState(ReactStates.Passive); - StartMovement(new AssistanceMovementGenerator(x, y, z), MovementSlot.Active); + Add(new AssistanceMovementGenerator(EventId.AssistMove, x, y, z)); } else Log.outError(LogFilter.Server, $"MotionMaster::MoveSeekAssistance: {_owner.GetGUID()}, attempted to seek assistance"); @@ -554,7 +866,7 @@ namespace Game.Movement public void MoveSeekAssistanceDistract(uint time) { if (_owner.IsCreature()) - StartMovement(new AssistanceDistractMovementGenerator(time), MovementSlot.Active); + Add(new AssistanceDistractMovementGenerator(time, _owner.GetOrientation())); else Log.outError(LogFilter.Server, $"MotionMaster::MoveSeekAssistanceDistract: {_owner.GetGUID()} attempted to call distract after assistance"); } @@ -566,9 +878,13 @@ namespace Game.Movement if (path < CliDB.TaxiPathNodesByPath.Count) { Log.outDebug(LogFilter.Server, $"MotionMaster::MoveTaxiFlight: {_owner.GetGUID()} taxi to Path Id: {path} (node {pathnode})"); + + // Only one FLIGHT_MOTION_TYPE is allowed + Remove(MovementGeneratorType.Flight); + FlightPathMovementGenerator movement = new(); movement.LoadPath(_owner.ToPlayer()); - StartMovement(movement, MovementSlot.Controlled); + Add(movement); } else Log.outError(LogFilter.Movement, $"MotionMaster::MoveTaxiFlight: '{_owner.GetGUID()}', attempted taxi to non-existing path Id: {path} (node: {pathnode})"); @@ -578,12 +894,14 @@ namespace Game.Movement Log.outError(LogFilter.Movement, $"MotionMaster::MoveTaxiFlight: '{_owner.GetGUID()}', attempted taxi to path Id: {path} (node: {pathnode})"); } - public void MoveDistract(uint timer) + public void MoveDistract(uint timer, float orientation) { + /* if (_slot[(int)MovementSlot.Controlled] != null) return; + */ - StartMovement(new DistractMovementGenerator(timer), MovementSlot.Controlled); + Add(new DistractMovementGenerator(timer, orientation)); } public void MovePath(uint pathId, bool repeatable) @@ -591,65 +909,95 @@ namespace Game.Movement if (pathId == 0) return; - StartMovement(new WaypointMovementGenerator(pathId, repeatable), MovementSlot.Idle); + Add(new WaypointMovementGenerator(pathId, repeatable), MovementSlot.Default); } public void MovePath(WaypointPath path, bool repeatable) { - StartMovement(new WaypointMovementGenerator(path, repeatable), MovementSlot.Idle); + Add(new WaypointMovementGenerator(path, repeatable), MovementSlot.Default); } - public void MoveRotate(uint time, RotateDirection direction) + public void MoveRotate(uint id, uint time, RotateDirection direction) { if (time == 0) return; - StartMovement(new RotateMovementGenerator(time, direction), MovementSlot.Active); + Add(new RotateMovementGenerator(id, time, direction)); } 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); + Add(new FormationMovementGenerator(id, destination, moveType, forceRun, forceOrientation)); } - public void LaunchMoveSpline(MoveSplineInit init, uint id = 0, MovementSlot slot = MovementSlot.Active, MovementGeneratorType type = MovementGeneratorType.Effect) + public void LaunchMoveSpline(MoveSplineInit init, uint id = 0, MovementGeneratorPriority priority = MovementGeneratorPriority.Normal, MovementGeneratorType type = MovementGeneratorType.Effect) { if (IsInvalidMovementGeneratorType(type)) { - Log.outDebug(LogFilter.Movement, $"MotionMaster::LaunchMoveSpline: '{_owner.GetGUID()}', tried to launch a spline with an invalid MovementGeneratorType: {type} (Id: {id}, Slot: {slot})"); + Log.outDebug(LogFilter.Movement, $"MotionMaster::LaunchMoveSpline: '{_owner.GetGUID()}', tried to launch a spline with an invalid MovementGeneratorType: {type} (Id: {id}, Priority: {priority})"); return; } - StartMovement(new GenericMovementGenerator(init, type, id), slot); + GenericMovementGenerator movement = new GenericMovementGenerator(init, type, id); + movement.Priority = priority; + Add(movement); } - void Pop() + void Pop(bool active, bool movementInform) { - if (Empty()) + MovementGenerator pointer = _generators.FirstOrDefault(); + _generators.Remove(pointer); + Delete(pointer, active, movementInform); + } + + void DirectInitialize() + { + // Clear ALL movement generators (including default) + DirectClearDefault(); + DirectClear(); + InitializeDefault(); + } + + void DirectClear() + { + // First delete Top + if (!_generators.Empty()) + Pop(true, false); + + // Then the rest + while (!_generators.Empty()) + Pop(false, false); + + // Make sure the storage is empty + ClearBaseUnitStates(); + } + + void DirectClearDefault() + { + if (_defaultGenerator != null) + DeleteDefault(_generators.Empty(), false); + } + + void DirectClear(Func filter) + { + if (_generators.Empty()) return; - _slot[_top] = null; - while (!Empty() && Top() == null) - --_top; + MovementGenerator top = GetCurrentMovementGenerator(); + foreach (var movement in _generators.ToList()) + { + if (filter(movement)) + { + _generators.Remove(movement); + Delete(movement, movement == top, false); + } + } } - bool NeedInitTop() - { - if (Empty()) - return false; - - return _initialize[_top]; - } - - void InitTop() - { - Top().Initialize(_owner); - _initialize[_top] = false; - } - - void StartMovement(IMovementGenerator m, MovementSlot slot) + void DirectAdd(MovementGenerator movement, MovementSlot slot = MovementSlot.Active) { + /* IMovementGenerator curr = _slot[(int)slot]; if (curr != null) { @@ -672,159 +1020,122 @@ namespace Game.Movement _initialize[(int)slot] = false; m.Initialize(_owner); } + */ + + /* + * NOTE: This mimics old behaviour: only one MOTION_SLOT_IDLE, MOTION_SLOT_ACTIVE, MOTION_SLOT_CONTROLLED + * On future changes support for multiple will be added + */ + + + switch (slot) + { + case MovementSlot.Default: + if (_defaultGenerator != null) + _defaultGenerator.Finalize(_owner, _generators.Empty(), false); + + _defaultGenerator = movement; + if (IsStatic(movement)) + AddFlag(MotionMasterFlags.StaticInitializationPending); + break; + case MovementSlot.Active: + if (!_generators.Empty()) + { + if (movement.Priority >= _generators.FirstOrDefault().Priority) + { + MovementGenerator pointer = _generators.FirstOrDefault(); + if (movement.Priority == pointer.Priority) + { + _generators.Remove(pointer); + Delete(pointer, true, false); + } + else + pointer.Deactivate(_owner); + } + else + { + var pointer = _generators.FirstOrDefault(a => a.Priority == movement.Priority); + if (pointer != null) + { + _generators.Remove(pointer); + Delete(pointer, false, false); + } + } + } + else + _defaultGenerator.Deactivate(_owner); + + _generators.Add(movement); + AddBaseUnitState(movement); + break; + default: + break; + } } - void DirectClean(bool reset) + void Delete(MovementGenerator movement, bool active, bool movementInform) { - while (Size() > 1) - { - IMovementGenerator curr = Top(); - Pop(); - if (curr != null) - DirectDelete(curr); - } + movement.Finalize(_owner, active, movementInform); + ClearBaseUnitState(movement); + } - if (Empty()) + void DeleteDefault(bool active, bool movementInform) + { + _defaultGenerator.Finalize(_owner, active, movementInform); + _defaultGenerator = GetIdleMovementGenerator(); + AddFlag(MotionMasterFlags.StaticInitializationPending); + } + + void AddBaseUnitState(MovementGenerator movement) + { + if (movement == null || movement.BaseUnitState == 0) return; - if (NeedInitTop()) - InitTop(); - else if (reset) - Top().Reset(_owner); + _baseUnitStatesMap.Add((uint)movement.BaseUnitState, movement); + _owner.AddUnitState(movement.BaseUnitState); } - void DirectClean(MovementSlot slot) + void ClearBaseUnitState(MovementGenerator movement) { - IMovementGenerator motion = GetMotionSlot(slot); - if (motion != null) - { - _slot[(int)slot] = null; - DirectDelete(motion); - } - - while (!Empty() && Top() == null) - --_top; - - if (Empty()) - Initialize(); - else if (NeedInitTop()) - InitTop(); - } - - void DelayedClean() - { - while (Size() > 1) - { - IMovementGenerator curr = Top(); - Pop(); - if (curr != null) - DelayedDelete(curr); - } - } - - void DelayedClean(MovementSlot slot) - { - IMovementGenerator motion = GetMotionSlot(slot); - if (motion != null) - { - _slot[(int)slot] = null; - DelayedDelete(motion); - } - - while (!Empty() && Top() == null) - --_top; - } - - void DirectExpire(bool reset) - { - if (Size() > 1) - { - IMovementGenerator curr = Top(); - Pop(); - DirectDelete(curr); - } - - while (!Empty() && Top() == null)//not sure this will work - --_top; - - if (Empty()) - Initialize(); - else if (NeedInitTop()) - InitTop(); - else if (reset) - Top().Reset(_owner); - } - - void DelayedExpire() - { - if (Size() > 1) - { - IMovementGenerator curr = Top(); - Pop(); - DelayedDelete(curr); - } - - while (!Empty() && Top() == null) - --_top; - } - - void DirectDelete(IMovementGenerator curr) - { - if (IsStatic(curr)) - return; - curr.Finalize(_owner); - } - - void DelayedDelete(IMovementGenerator curr) - { - if (IsStatic(curr)) + if (movement == null || movement.BaseUnitState == 0) return; - _expireList.Add(curr); + _baseUnitStatesMap.Remove((uint)movement.BaseUnitState, movement); + if (!_baseUnitStatesMap.ContainsKey(movement.BaseUnitState)) + _owner.ClearUnitState(movement.BaseUnitState); } - void ClearExpireList() + void ClearBaseUnitStates() { - for (int i = 0; i < _expireList.Count; ++i) - DirectDelete(_expireList[i]); + uint unitState = 0; + foreach (var itr in _baseUnitStatesMap) + unitState |= itr.Key; - _expireList.Clear(); - - if (Empty()) - Initialize(); - else if (NeedInitTop()) - InitTop(); - else if (_cleanFlag.HasAnyFlag(MotionMasterCleanFlag.Reset)) - Top().Reset(_owner); - - _cleanFlag &= ~MotionMasterCleanFlag.Reset; + _owner.ClearUnitState((UnitState)unitState); + _baseUnitStatesMap.Clear(); } - public bool Empty() { return (_top < 0); } + void AddFlag(MotionMasterFlags flag) { _flags |= flag; } + bool HasFlag(MotionMasterFlags flag) { return (_flags & flag) != 0; } + void RemoveFlag(MotionMasterFlags flag) { _flags &= ~flag; } - int Size() { return _top + 1; } - - public static uint SplineId + public static MovementGenerator GetIdleMovementGenerator() { - get { return splineId++; } + return staticIdleMovement; } - bool IsStatic(IMovementGenerator movement) + public static bool IsStatic(MovementGenerator movement) { - return (movement == staticIdleMovement); + return (movement == GetIdleMovementGenerator()); } public static bool IsInvalidMovementGeneratorType(MovementGeneratorType type) { return type == MovementGeneratorType.MaxDB || type >= MovementGeneratorType.Max; } public static bool IsInvalidMovementSlot(MovementSlot slot) { return slot >= MovementSlot.Max; } - static uint splineId; - - Unit _owner { get; } - IMovementGenerator[] _slot = new IMovementGenerator[(int)MovementSlot.Max]; - MotionMasterCleanFlag _cleanFlag; - bool[] _initialize = new bool[(int)MovementSlot.Max]; - int _top; - List _expireList = new(); + public static uint SplineId + { + get { return splineId++; } + } } public class JumpArrivalCastArgs @@ -833,13 +1144,6 @@ namespace Game.Movement public ObjectGuid Target; } - enum MotionMasterCleanFlag - { - None = 0, - Update = 1, // Clear or Expire called from update - Reset = 2 // Flag if need top().Reset() - } - public struct ChaseRange { // this contains info that informs how we should path! diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index faa8e63df..fd1f0c8b8 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -2439,8 +2439,6 @@ namespace Game.Spells [AuraEffectHandler(AuraType.ModPossessPet)] void HandleModPossessPet(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) { - // Used by spell "Eyes of the Beast" - if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) return; @@ -2459,9 +2457,6 @@ namespace Game.Spells if (caster.ToPlayer().GetPet() != pet) return; - // Must clear current motion or pet leashes back to owner after a few yards - // when under spell 'Eyes of the Beast' - pet.GetMotionMaster().Clear(); pet.SetCharmedBy(caster, CharmType.Possess, aurApp); } else @@ -2475,13 +2470,9 @@ namespace Game.Spells // Reinitialize the pet bar or it will appear greyed out caster.ToPlayer().PetSpellInitialize(); - // Follow owner only if not fighting or owner didn't click "stay" at new location - // This may be confusing because pet bar shows "stay" when under the spell but it retains - // the "follow" flag. Player MUST click "stay" while under the spell. + // TODO: remove this if (pet.GetVictim() == null && !pet.GetCharmInfo().HasCommandState(CommandStates.Stay)) - { pet.GetMotionMaster().MoveFollow(caster, SharedConst.PetFollowDist, pet.GetFollowAngle()); - } } } } diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 3fcfe3138..198b2ba2c 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -1922,11 +1922,7 @@ namespace Game.Spells if (unitTarget.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing)) return; - if (unitTarget.IsTypeId(TypeId.Unit)) - unitTarget.GetMotionMaster().MoveDistract((uint)(damage * Time.InMilliseconds)); - - unitTarget.StopMoving(); - unitTarget.SetFacingTo(unitTarget.GetAbsoluteAngle(destTarget)); + unitTarget.GetMotionMaster().MoveDistract((uint)(damage * Time.InMilliseconds), unitTarget.GetAbsoluteAngle(destTarget)); } [SpellEffectHandler(SpellEffectName.Pickpocket)] diff --git a/Source/Scripts/Pets/DeathKnight.cs b/Source/Scripts/Pets/DeathKnight.cs index 4b3c3ec67..ec8da43e5 100644 --- a/Source/Scripts/Pets/DeathKnight.cs +++ b/Source/Scripts/Pets/DeathKnight.cs @@ -96,7 +96,7 @@ namespace Scripts.Pets float x = me.GetPositionX() + 20 * (float)Math.Cos(me.GetOrientation()); float y = me.GetPositionY() + 20 * (float)Math.Sin(me.GetOrientation()); float z = me.GetPositionZ() + 40; - me.GetMotionMaster().Clear(false); + me.GetMotionMaster().Clear(); me.GetMotionMaster().MovePoint(0, x, y, z); // Despawn as soon as possible diff --git a/Source/Scripts/Pets/Mage.cs b/Source/Scripts/Pets/Mage.cs index 5aa109675..3511ea6a8 100644 --- a/Source/Scripts/Pets/Mage.cs +++ b/Source/Scripts/Pets/Mage.cs @@ -189,8 +189,8 @@ namespace Scripts.Pets me.CombatStop(true); if (owner && !me.HasUnitState(UnitState.Follow)) { - me.GetMotionMaster().Clear(false); - me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle(), MovementSlot.Active); + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveFollow(owner, SharedConst.PetFollowDist, me.GetFollowAngle()); } } } diff --git a/Source/Scripts/World/EmeraldDragons.cs b/Source/Scripts/World/EmeraldDragons.cs index 71d72950f..655ae5b09 100644 --- a/Source/Scripts/World/EmeraldDragons.cs +++ b/Source/Scripts/World/EmeraldDragons.cs @@ -186,13 +186,13 @@ namespace Scripts.World.EmeraldDragons if (target) { _roamTimer = RandomHelper.URand(15000, 30000); - me.GetMotionMaster().Clear(false); + me.GetMotionMaster().Clear(); me.GetMotionMaster().MoveChase(target, 0.2f); } else { _roamTimer = 2500; - me.GetMotionMaster().Clear(false); + me.GetMotionMaster().Clear(); me.GetMotionMaster().MoveRandom(25.0f); } // Seeping fog movement is slow enough for a player to be able to walk backwards and still outpace it diff --git a/Source/Scripts/World/NpcSpecial.cs b/Source/Scripts/World/NpcSpecial.cs index d281c7a8d..f6a475c23 100644 --- a/Source/Scripts/World/NpcSpecial.cs +++ b/Source/Scripts/World/NpcSpecial.cs @@ -2323,7 +2323,7 @@ namespace Scripts.World.NpcSpecial init.DisableTransportPathTransformations(); init.MoveTo(x, y, z, false); init.SetFacing(o); - who.GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleBoard, MovementSlot.Controlled); + who.GetMotionMaster().LaunchMoveSpline(init, EventId.VehicleBoard, MovementGeneratorPriority.Highest); who.m_Events.AddEvent(new CastFoodSpell(who, SpellIds.ChairSpells[who.GetEntry()]), who.m_Events.CalculateTime(1000)); Creature creature = who.ToCreature(); if (creature)