| | | 1 | | using Chronicler; |
| | | 2 | | using FixedMathSharp; |
| | | 3 | | using System; |
| | | 4 | | using Trailblazer.Pathing; |
| | | 5 | | |
| | | 6 | | namespace Trailblazer.Navigation.Steering; |
| | | 7 | | |
| | | 8 | | internal enum PathRequestRecordKind |
| | | 9 | | { |
| | | 10 | | None, |
| | | 11 | | AStar, |
| | | 12 | | FlowField, |
| | | 13 | | Volume, |
| | | 14 | | Hybrid |
| | | 15 | | } |
| | | 16 | | |
| | | 17 | | /// <summary> |
| | | 18 | | /// Stores a serializable path-request shape and rebuilds it on load. |
| | | 19 | | /// </summary> |
| | | 20 | | internal sealed class PathRequestRecord : IRecordable |
| | | 21 | | { |
| | | 22 | | private const int NoWaypointIndex = -1; |
| | | 23 | | |
| | | 24 | | public PathRequestRecordKind Kind; |
| | | 25 | | |
| | | 26 | | public Vector3d Origin; |
| | | 27 | | |
| | | 28 | | public Vector3d TargetPosition; |
| | | 29 | | |
| | 101 | 30 | | public Fixed64 UnitSize = Fixed64.One; |
| | | 31 | | |
| | | 32 | | public bool AllowUnwalkableEndpoints; |
| | | 33 | | |
| | | 34 | | public bool AllowTraversalTransitions; |
| | | 35 | | |
| | | 36 | | public int MaxPathSearchRange; |
| | | 37 | | |
| | 101 | 38 | | public Fixed64 MaxClimbHeight = Fixed64.One; |
| | | 39 | | |
| | | 40 | | public HeuristicMethod AStarHeuristic = HeuristicMethod.Manhattan; |
| | | 41 | | |
| | 101 | 42 | | public int FlowFieldExtraFloodRange = FlowFieldPathRequest.DefaultExtraFloodRange; |
| | | 43 | | |
| | 101 | 44 | | public TraversalMedium Medium = TraversalMedium.Gas; |
| | | 45 | | |
| | | 46 | | public bool HasGuide; |
| | | 47 | | |
| | 101 | 48 | | public int WaypointIndex = NoWaypointIndex; |
| | | 49 | | |
| | | 50 | | public void Capture(IPathRequest? request, IGuide? guide) |
| | | 51 | | { |
| | 55 | 52 | | Reset(); |
| | 55 | 53 | | if (request == null) |
| | 14 | 54 | | return; |
| | | 55 | | |
| | 41 | 56 | | Origin = request.Origin; |
| | 41 | 57 | | TargetPosition = request.TargetPosition; |
| | 41 | 58 | | UnitSize = request.UnitSize; |
| | 41 | 59 | | AllowUnwalkableEndpoints = request.AllowUnwalkableEndpoints; |
| | 41 | 60 | | MaxPathSearchRange = request.MaxPathSearchRange; |
| | 41 | 61 | | HasGuide = guide != null; |
| | | 62 | | |
| | | 63 | | switch (request) |
| | | 64 | | { |
| | | 65 | | case AStarPathRequest aStar: |
| | 19 | 66 | | Kind = PathRequestRecordKind.AStar; |
| | 19 | 67 | | AllowTraversalTransitions = aStar.AllowTraversalTransitions; |
| | 19 | 68 | | AStarHeuristic = aStar.Heuristic; |
| | 19 | 69 | | MaxClimbHeight = aStar.MaxClimbHeight; |
| | 19 | 70 | | break; |
| | | 71 | | |
| | | 72 | | case FlowFieldPathRequest flowField: |
| | 5 | 73 | | Kind = PathRequestRecordKind.FlowField; |
| | 5 | 74 | | AllowTraversalTransitions = flowField.AllowTraversalTransitions; |
| | 5 | 75 | | MaxClimbHeight = flowField.MaxClimbHeight; |
| | 5 | 76 | | FlowFieldExtraFloodRange = flowField.ExtraFloodRange; |
| | 5 | 77 | | break; |
| | | 78 | | |
| | | 79 | | case VolumePathRequest volume: |
| | 14 | 80 | | Kind = PathRequestRecordKind.Volume; |
| | 14 | 81 | | AStarHeuristic = volume.Heuristic; |
| | 14 | 82 | | Medium = volume.Medium; |
| | 14 | 83 | | break; |
| | | 84 | | |
| | | 85 | | case HybridPathRequest hybrid: |
| | 2 | 86 | | Kind = PathRequestRecordKind.Hybrid; |
| | 2 | 87 | | AStarHeuristic = hybrid.Heuristic; |
| | 2 | 88 | | MaxClimbHeight = hybrid.MaxClimbHeight; |
| | 2 | 89 | | break; |
| | | 90 | | |
| | | 91 | | default: |
| | 1 | 92 | | throw new NotSupportedException( |
| | 1 | 93 | | $"Unable to record steering path request type '{request.GetType().Name}'."); |
| | | 94 | | } |
| | | 95 | | |
| | 40 | 96 | | if (guide is IWaypointGuide waypointGuide) |
| | 13 | 97 | | WaypointIndex = waypointGuide.CurrentWaypointIndex; |
| | 40 | 98 | | } |
| | | 99 | | |
| | | 100 | | public bool TryCreateRequest(TrailblazerWorldContext context, out IPathRequest? request) |
| | | 101 | | { |
| | 57 | 102 | | request = null; |
| | 57 | 103 | | PathRequestContextResolver.ThrowIfUnusable(context); |
| | | 104 | | |
| | 57 | 105 | | switch (Kind) |
| | | 106 | | { |
| | | 107 | | case PathRequestRecordKind.None: |
| | 13 | 108 | | return true; |
| | | 109 | | |
| | | 110 | | case PathRequestRecordKind.AStar: |
| | 20 | 111 | | AStarPathRequest? aStar = AStarPathRequest.Create( |
| | 20 | 112 | | context, |
| | 20 | 113 | | Origin, |
| | 20 | 114 | | TargetPosition, |
| | 20 | 115 | | UnitSize, |
| | 20 | 116 | | AStarHeuristic, |
| | 20 | 117 | | AllowUnwalkableEndpoints, |
| | 20 | 118 | | AllowTraversalTransitions); |
| | 20 | 119 | | if (aStar == null) |
| | 4 | 120 | | return false; |
| | | 121 | | |
| | 16 | 122 | | aStar.MaxClimbHeight = MaxClimbHeight; |
| | 16 | 123 | | if (MaxPathSearchRange > 0) |
| | 16 | 124 | | aStar.MaxPathSearchRange = MaxPathSearchRange; |
| | | 125 | | |
| | 16 | 126 | | request = aStar; |
| | 16 | 127 | | return true; |
| | | 128 | | |
| | | 129 | | case PathRequestRecordKind.FlowField: |
| | 5 | 130 | | FlowFieldPathRequest? flowField = FlowFieldPathRequest.Create( |
| | 5 | 131 | | context, |
| | 5 | 132 | | Origin, |
| | 5 | 133 | | TargetPosition, |
| | 5 | 134 | | UnitSize, |
| | 5 | 135 | | AllowUnwalkableEndpoints, |
| | 5 | 136 | | AllowTraversalTransitions); |
| | 5 | 137 | | if (flowField == null) |
| | 1 | 138 | | return false; |
| | | 139 | | |
| | 4 | 140 | | flowField.MaxClimbHeight = MaxClimbHeight; |
| | 4 | 141 | | flowField.ExtraFloodRange = FlowFieldExtraFloodRange; |
| | 4 | 142 | | if (MaxPathSearchRange > 0) |
| | 4 | 143 | | flowField.MaxPathSearchRange = MaxPathSearchRange; |
| | | 144 | | |
| | 4 | 145 | | request = flowField; |
| | 4 | 146 | | return true; |
| | | 147 | | |
| | | 148 | | case PathRequestRecordKind.Volume: |
| | 15 | 149 | | VolumePathRequest? volume = VolumePathRequest.Create( |
| | 15 | 150 | | context, |
| | 15 | 151 | | Origin, |
| | 15 | 152 | | TargetPosition, |
| | 15 | 153 | | UnitSize, |
| | 15 | 154 | | AStarHeuristic, |
| | 15 | 155 | | AllowUnwalkableEndpoints, |
| | 15 | 156 | | Medium); |
| | 15 | 157 | | if (volume == null) |
| | 1 | 158 | | return false; |
| | | 159 | | |
| | 14 | 160 | | if (MaxPathSearchRange > 0) |
| | 14 | 161 | | volume.MaxPathSearchRange = MaxPathSearchRange; |
| | | 162 | | |
| | 14 | 163 | | request = volume; |
| | 14 | 164 | | return true; |
| | | 165 | | |
| | | 166 | | case PathRequestRecordKind.Hybrid: |
| | 3 | 167 | | HybridPathRequest? hybrid = HybridPathRequest.Create( |
| | 3 | 168 | | context, |
| | 3 | 169 | | Origin, |
| | 3 | 170 | | TargetPosition, |
| | 3 | 171 | | UnitSize, |
| | 3 | 172 | | AStarHeuristic, |
| | 3 | 173 | | MaxClimbHeight, |
| | 3 | 174 | | AllowUnwalkableEndpoints); |
| | 3 | 175 | | if (hybrid == null) |
| | 1 | 176 | | return false; |
| | | 177 | | |
| | 2 | 178 | | if (MaxPathSearchRange > 0) |
| | 2 | 179 | | hybrid.MaxPathSearchRange = MaxPathSearchRange; |
| | | 180 | | |
| | 2 | 181 | | request = hybrid; |
| | 2 | 182 | | return true; |
| | | 183 | | |
| | | 184 | | default: |
| | 1 | 185 | | return false; |
| | | 186 | | } |
| | | 187 | | } |
| | | 188 | | |
| | | 189 | | public bool TryCreateGuide(IPathRequest? request, out IGuide? guide) |
| | | 190 | | { |
| | 17 | 191 | | guide = null; |
| | 17 | 192 | | if (!HasGuide || request == null) |
| | 1 | 193 | | return false; |
| | | 194 | | |
| | 16 | 195 | | if (!request.Context.Guides.RequestGuide(request, out guide) || guide == null) |
| | 2 | 196 | | return false; |
| | | 197 | | |
| | 14 | 198 | | if (guide is AStarGuide aStarGuide) |
| | 4 | 199 | | RestoreWaypointIndex(aStarGuide); |
| | 10 | 200 | | else if (guide is VolumeGuide volumeGuide) |
| | 5 | 201 | | RestoreWaypointIndex(volumeGuide); |
| | 5 | 202 | | else if (guide is HybridGuide hybridGuide) |
| | 1 | 203 | | RestoreWaypointIndex(hybridGuide); |
| | | 204 | | |
| | 14 | 205 | | return true; |
| | | 206 | | } |
| | | 207 | | |
| | | 208 | | public void Reset() |
| | | 209 | | { |
| | 59 | 210 | | Kind = PathRequestRecordKind.None; |
| | 59 | 211 | | Origin = Vector3d.Zero; |
| | 59 | 212 | | TargetPosition = Vector3d.Zero; |
| | 59 | 213 | | UnitSize = Fixed64.One; |
| | 59 | 214 | | AllowUnwalkableEndpoints = false; |
| | 59 | 215 | | AllowTraversalTransitions = false; |
| | 59 | 216 | | MaxPathSearchRange = 0; |
| | 59 | 217 | | MaxClimbHeight = Fixed64.One; |
| | 59 | 218 | | AStarHeuristic = HeuristicMethod.Manhattan; |
| | 59 | 219 | | FlowFieldExtraFloodRange = FlowFieldPathRequest.DefaultExtraFloodRange; |
| | 59 | 220 | | Medium = TraversalMedium.Gas; |
| | 59 | 221 | | HasGuide = false; |
| | 59 | 222 | | WaypointIndex = NoWaypointIndex; |
| | 59 | 223 | | } |
| | | 224 | | |
| | | 225 | | public void RecordData(IChronicler chronicler) |
| | | 226 | | { |
| | 90 | 227 | | RecordValues.Look(chronicler, ref Kind, "Kind", PathRequestRecordKind.None); |
| | 90 | 228 | | RecordValues.Look(chronicler, ref Origin, "Origin", Vector3d.Zero); |
| | 90 | 229 | | RecordValues.Look(chronicler, ref TargetPosition, "TargetPosition", Vector3d.Zero); |
| | 90 | 230 | | RecordValues.Look(chronicler, ref UnitSize, "UnitSize", Fixed64.One); |
| | 90 | 231 | | RecordValues.Look(chronicler, ref AllowUnwalkableEndpoints, "AllowUnwalkableEndpoints", false); |
| | 90 | 232 | | RecordValues.Look(chronicler, ref AllowTraversalTransitions, "AllowTraversalTransitions", false); |
| | 90 | 233 | | RecordValues.Look(chronicler, ref MaxPathSearchRange, "MaxPathSearchRange", 0); |
| | 90 | 234 | | RecordValues.Look(chronicler, ref MaxClimbHeight, "MaxClimbHeight", Fixed64.One); |
| | 90 | 235 | | RecordValues.Look(chronicler, ref AStarHeuristic, "AStarHeuristic", HeuristicMethod.Manhattan); |
| | 90 | 236 | | RecordValues.Look(chronicler, ref FlowFieldExtraFloodRange, "FlowFieldExtraFloodRange", FlowFieldPathRequest.Def |
| | 90 | 237 | | RecordValues.Look(chronicler, ref Medium, "Medium", TraversalMedium.Gas); |
| | 90 | 238 | | RecordValues.Look(chronicler, ref HasGuide, "HasGuide", false); |
| | 90 | 239 | | RecordValues.Look(chronicler, ref WaypointIndex, "WaypointIndex", NoWaypointIndex); |
| | 90 | 240 | | } |
| | | 241 | | |
| | | 242 | | private void RestoreWaypointIndex(AStarGuide guide) |
| | | 243 | | { |
| | 4 | 244 | | if (WaypointIndex <= 0) |
| | 0 | 245 | | return; |
| | | 246 | | |
| | 12 | 247 | | while (guide.CurrentWaypointIndex < WaypointIndex |
| | 12 | 248 | | && guide.TryGetWaypointAt(guide.CurrentWaypointIndex + 1, out _)) |
| | | 249 | | { |
| | 8 | 250 | | guide.AdvanceWaypoint(); |
| | | 251 | | } |
| | 4 | 252 | | } |
| | | 253 | | |
| | | 254 | | private void RestoreWaypointIndex(VolumeGuide guide) |
| | | 255 | | { |
| | 5 | 256 | | if (WaypointIndex <= 0) |
| | 0 | 257 | | return; |
| | | 258 | | |
| | 12 | 259 | | while (guide.CurrentWaypointIndex < WaypointIndex |
| | 12 | 260 | | && guide.TryGetWaypointAt(guide.CurrentWaypointIndex + 1, out _)) |
| | | 261 | | { |
| | 7 | 262 | | guide.AdvanceWaypoint(); |
| | | 263 | | } |
| | 5 | 264 | | } |
| | | 265 | | |
| | | 266 | | private void RestoreWaypointIndex(HybridGuide guide) |
| | | 267 | | { |
| | 1 | 268 | | if (WaypointIndex <= 0) |
| | 0 | 269 | | return; |
| | | 270 | | |
| | 2 | 271 | | while (guide.CurrentWaypointIndex < WaypointIndex |
| | 2 | 272 | | && guide.TryGetWaypointAt(guide.CurrentWaypointIndex + 1, out _)) |
| | | 273 | | { |
| | 1 | 274 | | guide.AdvanceWaypoint(); |
| | | 275 | | } |
| | 1 | 276 | | } |
| | | 277 | | } |