diff --git a/Source/Game/Movement/Generators/WaypointMovement.cs b/Source/Game/Movement/Generators/WaypointMovement.cs index 3b96e1449..c2b50d85c 100644 --- a/Source/Game/Movement/Generators/WaypointMovement.cs +++ b/Source/Game/Movement/Generators/WaypointMovement.cs @@ -8,11 +8,13 @@ using Game.Scripting.v2; using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; namespace Game.Movement { public class WaypointMovementGenerator : MovementGeneratorMedium { + List _waypointTransitionSplinePoints = new(); TimeTracker _nextMoveTime; uint _pathId; bool _repeating; @@ -51,6 +53,8 @@ namespace Game.Movement if (duration.HasValue) _duration = new(duration.Value); + + _path.BuildSegments(); } public WaypointMovementGenerator(WaypointPath path, bool repeating = true, TimeSpan? duration = null, float? speed = null, MovementWalkRunSpeedSelectionMode speedSelectionMode = MovementWalkRunSpeedSelectionMode.Default, @@ -221,6 +225,19 @@ namespace Game.Movement if (owner.GetTransGUID().IsEmpty()) owner.SetHomePosition(owner.GetPosition()); + // handle switching points in continuous segments + if (_waypointTransitionSplinePoints.Count > 1 && owner.MoveSpline.CurrentPathIdx() >= _waypointTransitionSplinePoints.First()) + { + OnArrived(owner); + _waypointTransitionSplinePoints.Remove(_waypointTransitionSplinePoints.First()); + if (ComputeNextNode()) + { + CreatureAI ai = owner.GetAI(); + if (ai != null) + ai.WaypointStarted(_path.Nodes[_currentNode].Id, _path.Id); + } + } + // relaunch movement if its speed has changed if (HasFlag(MovementGeneratorFlags.SpeedUpdatePending)) StartMove(owner, true); @@ -340,6 +357,7 @@ namespace Game.Movement bool transportPath = !owner.GetTransGUID().IsEmpty(); + int previousNode = _currentNode; if (HasFlag(MovementGeneratorFlags.InformEnabled) && HasFlag(MovementGeneratorFlags.Initialized)) { if (ComputeNextNode()) @@ -396,7 +414,70 @@ namespace Game.Movement } 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]; + + List segment = new Func>(() => + { + // find the continuous segment that our destination waypoint is on + var segmentIndex = _path.ContinuousSegments.FindIndex(segmentRange => + { + bool isInSegmentRange(int node) { return node >= segmentRange.First && node < segmentRange.Last + segmentRange.Last; }; + return isInSegmentRange(_currentNode) && isInSegmentRange(previousNode); + }); + + // handle path returning directly from last point to first + if (segmentIndex == -1) + { + if (_currentNode != 0 || previousNode != _path.Nodes.Count - 1) + return _path.Nodes[_currentNode..1];//, 1); + + var slice3 = _path.Nodes[2..]; + + segmentIndex = 0; + } + + if (!_isReturningToStart) + return _path.Nodes[_currentNode..(_path.ContinuousSegments[segmentIndex].Last - (_currentNode - _path.ContinuousSegments[segmentIndex].First))]; + + return _path.Nodes[_path.ContinuousSegments[segmentIndex].First..(_currentNode - _path.ContinuousSegments[segmentIndex].First + 1)]; + })(); + + WaypointNode lastWaypointForSegment = !_isReturningToStart ? segment.Last() : segment.First(); + + _waypointTransitionSplinePoints.Clear(); + List points = new(); + void fillPath(List list) + { + PathGenerator generator = null; + if (_generatePath) + generator = new(owner); + + Position source = owner.GetPosition(); + points.Add(new Vector3(source.GetPositionX(), source.GetPositionY(), source.GetPositionZ())); + + foreach (var node in list) + { + if (generator != null) + { + bool result = generator.CalculatePath(source.GetPositionX(), source.GetPositionY(), source.GetPositionZ(), node.X, node.Y, node.Z); + if (result && (generator.GetPathType() & PathType.NoPath) == 0) + points.AddRange(generator.GetPath()[1..]); + else + generator = null; // when path generation to a waypoint fails, add all remaining points without pathfinding (preserve legacy behavior of MoveSplineInit::MoveTo) + } + + if (generator == null) + points.Add(new Vector3(node.X, node.Y, node.Z)); + + _waypointTransitionSplinePoints.Add(points.Count - 1); + + source.Relocate(node.X, node.Y, node.Z); + } + }; + + if (!_isReturningToStart) + fillPath(segment); + else + fillPath(segment[^0..]); RemoveFlag(MovementGeneratorFlags.Transitory | MovementGeneratorFlags.InformEnabled | MovementGeneratorFlags.TimedPaused); @@ -404,22 +485,28 @@ namespace Game.Movement MoveSplineInit init = new(owner); + // because steering flag can cause position on client to not be perfectly accurate, dont do it in combat + if (!owner.IsInCombat()) + init.SetSteering(); + //! If creature is on transport, we assume waypoints set in DB are already transport offsets if (transportPath) init.DisableTransportPathTransformations(); - init.MoveTo(waypoint.X, waypoint.Y, waypoint.Z, _generatePath); + init.MovebyPath(points.ToArray()); - if (waypoint.Orientation.HasValue && (waypoint.Delay != TimeSpan.Zero || _currentNode == _path.Nodes.Count - 1)) - init.SetFacing(waypoint.Orientation.Value); + if (lastWaypointForSegment.Orientation.HasValue && (lastWaypointForSegment.Delay != TimeSpan.Zero || _currentNode == _path.Nodes.Count - 1)) + init.SetFacing(lastWaypointForSegment.Orientation.Value); switch (_path.MoveType) { case WaypointMoveType.Land: init.SetAnimation(AnimTier.Ground); + init.SetFly(); break; case WaypointMoveType.Takeoff: init.SetAnimation(AnimTier.Fly); + init.SetFly(); break; case WaypointMoveType.Run: init.SetWalk(false); @@ -449,6 +536,25 @@ namespace Game.Movement if (_speed.HasValue) init.SetVelocity(_speed.Value); + if (init.Path().Count > 2) + { + Vector3 mid = (init.Path().First() + init.Path().Last()) / 2.0f; + var index = 0;// init.Path()[1..]; + var endIndex = (init.Path().Count - 2); + while (index != endIndex) + { + Vector3 offset = init.Path()[index] - mid; + if (MathF.Abs(offset.X) >= 128.0f || MathF.Abs(offset.Y) >= 128.0f || MathF.Abs(offset.Z) >= 64.0f) + { + // when distance is too great, send path in uncompressed state otherwise too much precision is lost on each point + init.SetUncompressed(); + break; + } + + ++index; + } + } + init.Launch(); // inform formation diff --git a/Source/Game/Movement/WaypointManager.cs b/Source/Game/Movement/WaypointManager.cs index 6c3f67f6d..4daf06abe 100644 --- a/Source/Game/Movement/WaypointManager.cs +++ b/Source/Game/Movement/WaypointManager.cs @@ -131,14 +131,15 @@ namespace Game void DoPostLoadingChecks() { - foreach (var path in _pathStorage) + foreach (var (pathId, pathInfo) in _pathStorage) { - WaypointPath pathInfo = path.Value; + pathInfo.BuildSegments(); + if (pathInfo.Nodes.Empty()) - Log.outError(LogFilter.Sql, $"PathId {pathInfo.Id} in `waypoint_path` has no assigned nodes in `waypoint_path_node`"); + Log.outError(LogFilter.Sql, $"PathId {pathId} in `waypoint_path` has no assigned nodes in `waypoint_path_node`"); if (pathInfo.Flags.HasFlag(WaypointPathFlags.FollowPathBackwardsFromEndToStart) && pathInfo.Nodes.Count < 2) - Log.outError(LogFilter.Sql, $"PathId {pathInfo.Id} in `waypoint_path` has FollowPathBackwardsFromEndToStart set, but only {pathInfo.Nodes.Count} nodes, requires {2}"); + Log.outError(LogFilter.Sql, $"PathId {pathId} in `waypoint_path` has FollowPathBackwardsFromEndToStart set, but only {pathInfo.Nodes.Count} nodes, requires {2}"); } } @@ -174,6 +175,10 @@ namespace Game { LoadPathNodesFromDB(result.GetFields()); } while (result.NextRow()); + + WaypointPath path = _pathStorage.LookupByKey(pathId); + if (path != null) + path.BuildSegments(); } public void VisualizePath(Unit owner, WaypointPath path, uint? displayId) @@ -329,6 +334,13 @@ namespace Game public class WaypointPath { + public List Nodes = new(); + public List ContinuousSegments = new(); + public uint Id; + public WaypointMoveType MoveType; + public WaypointPathFlags Flags = WaypointPathFlags.None; + public float? Velocity; + public WaypointPath() { } public WaypointPath(uint id, List nodes) { @@ -336,11 +348,31 @@ namespace Game Nodes = nodes; } - public List Nodes = new(); - public uint Id; - public WaypointMoveType MoveType; - public WaypointPathFlags Flags = WaypointPathFlags.None; - public float? Velocity; + public void BuildSegments() + { + ContinuousSegments.Add(new WaypointSegment()); + for (int i = 0; i < Nodes.Count; ++i) + { + var g = ContinuousSegments[^1]; + ++g.Last; + + // split on delay + if (i + 1 != Nodes.Count && Nodes[i].Delay != TimeSpan.Zero) + ContinuousSegments.Add(new WaypointSegment(i, 1)); + } + } + } + + public struct WaypointSegment + { + public int First; + public int Last; + + public WaypointSegment(int first, int last) + { + First = first; + Last = last; + } } public enum WaypointMoveType