diff --git a/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs b/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
index a64286813..888baa83f 100644
--- a/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
+++ b/Projects/UOContent.Tests/Fixtures/TestServerInitializer.cs
@@ -5,6 +5,7 @@ using System.Threading;
using Server.Items;
using Server.Misc;
using Server.Movement;
+using Server.PathAlgorithms;
using Server.Tests.Maps;
namespace Server.Tests;
@@ -56,6 +57,12 @@ internal static class TestServerInitializer
Server.Network.NetState.Configure();
TestMapDefinitions.ConfigureTestMapDefinitions();
+ // Production runs every static Configure() via AssemblyHandler.Invoke("Configure");
+ // the fixture calls a curated subset, so configure the pathfinding singleton here so
+ // BitmapAStarAlgorithm.Instance carries its configured MaxSearchNodes before any test
+ // calls Find. ServerConfiguration is already loaded above, so the setting resolves.
+ BitmapAStarAlgorithm.Configure();
+
World.Configure();
Timer.Init(0);
RaceDefinitions.Configure();
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
index f92fab0c9..ab9497729 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/BitmapAStarAlgorithmTests.cs
@@ -1,6 +1,6 @@
using Server.Engines.Pathing.Cache;
using Server.Mobiles;
-using Server.PathAlgorithms.BitmapAStar;
+using Server.PathAlgorithms;
using Server.Systems.FeatureFlags;
using Xunit;
using Xunit.Abstractions;
@@ -121,7 +121,7 @@ public class BitmapAStarAlgorithmTests
var y = sy;
foreach (var dir in result)
{
- Server.Movement.Movement.Offset(dir, ref x, ref y);
+ Movement.Movement.Offset(dir, ref x, ref y);
Assert.False(x == blockX && y == blockY,
$"path traversed blocker cell ({blockX},{blockY})");
}
@@ -180,7 +180,7 @@ public class BitmapAStarAlgorithmTests
var y = sy;
foreach (var dir in result)
{
- Server.Movement.Movement.Offset(dir, ref x, ref y);
+ Movement.Movement.Offset(dir, ref x, ref y);
Assert.False(x == blockX && y == blockY,
$"path traversed item-blocker cell ({blockX},{blockY})");
}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/PathfindRecorderTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/PathfindRecorderTests.cs
index da44a4f19..0930bad7a 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/PathfindRecorderTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/PathfindRecorderTests.cs
@@ -10,22 +10,11 @@ public class PathfindRecorderTests
private static string NewTempPath() =>
Path.Combine(Path.GetTempPath(), $"pathfind-recorder-{System.Guid.NewGuid():N}.jsonl");
- ///
- /// Reflection-set the static _outputPath without going through Configure (which
- /// reads from server.cfg) so tests don't poison the project's server.cfg.
- ///
- private static void OverrideOutputPath(string path)
- {
- typeof(PathfindRecorder).GetField("_outputPath",
- System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!
- .SetValue(null, path);
- }
-
[Fact]
public void Disabled_RecordIfEnabled_DoesNothing()
{
var path = NewTempPath();
- OverrideOutputPath(path);
+ PathfindRecorder.OutputPath = path;
PathfindRecorder.SetEnabled(false);
try
@@ -52,7 +41,7 @@ public class PathfindRecorderTests
public void Enabled_RecordIfEnabled_WritesValidJsonlLine()
{
var path = NewTempPath();
- OverrideOutputPath(path);
+ PathfindRecorder.OutputPath = path;
PathfindRecorder.SetEnabled(true);
try
@@ -95,7 +84,7 @@ public class PathfindRecorderTests
public void Enabled_RecordsCapabilityFlagsFromBaseCreature()
{
var path = NewTempPath();
- OverrideOutputPath(path);
+ PathfindRecorder.OutputPath = path;
PathfindRecorder.SetEnabled(true);
try
@@ -130,7 +119,7 @@ public class PathfindRecorderTests
public void SetEnabled_TogglingTwice_IsIdempotent()
{
var path = NewTempPath();
- OverrideOutputPath(path);
+ PathfindRecorder.OutputPath = path;
try
{
@@ -155,9 +144,6 @@ public class PathfindRecorderTests
private sealed class RecorderStub : Server.Mobiles.BaseCreature
{
- public RecorderStub(Serial serial) : base(serial)
- {
- Body = 0xC9;
- }
+ public RecorderStub(Serial serial) : base(serial) => Body = 0xC9;
}
}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
index d8e9aed01..ea3468759 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileTests.cs
@@ -55,7 +55,7 @@ public class StepCacheFileTests
expected[i] = cache.TryGetMask(map, x, y, standZ[i]);
}
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-roundtrip-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-roundtrip-{Guid.NewGuid():N}.swb");
try
{
var written = cache.SaveToFile(path, map.MapID);
@@ -109,7 +109,7 @@ public class StepCacheFileTests
var cache = StepCache.Instance;
cache.Clear();
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-missing-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-missing-{Guid.NewGuid():N}.swb");
Assert.False(cache.TryOpenLazyReader(path, mapId: 1));
Assert.Equal(0, cache.OpenLazyReaderCount);
}
@@ -120,7 +120,7 @@ public class StepCacheFileTests
var cache = StepCache.Instance;
cache.Clear();
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-badmagic-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-badmagic-{Guid.NewGuid():N}.swb");
try
{
File.WriteAllBytes(path, new byte[]
@@ -150,7 +150,7 @@ public class StepCacheFileTests
var map = Map.Maps[1];
cache.TryGetMask(map, 1500, 1600, sourceZ: 10);
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-stalehash-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-stalehash-{Guid.NewGuid():N}.swb");
try
{
cache.SaveToFile(path, map.MapID);
@@ -193,7 +193,7 @@ public class StepCacheFileTests
Assert.NotNull(map);
// Populate a handful of chunks.
- var coords = new (int, int)[]
+ var coords = new[]
{
(1500, 1600), (1516, 1600), (1500, 1616), (1516, 1616), (1532, 1600)
};
@@ -202,7 +202,7 @@ public class StepCacheFileTests
cache.TryGetMask(map, x, y, sourceZ: 10);
}
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-lazy-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-lazy-{Guid.NewGuid():N}.swb");
try
{
Assert.Equal(coords.Length, cache.SaveToFile(path, map.MapID));
@@ -269,7 +269,7 @@ public class StepCacheFileTests
chunk.SwimZE_Layer[cellIndex] = -7;
chunk.SwimZSE_Layer[cellIndex] = -7;
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-swim-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-swim-{Guid.NewGuid():N}.swb");
try
{
Assert.Equal(1, cache.SaveToFile(path, map.MapID));
@@ -314,7 +314,7 @@ public class StepCacheFileTests
var map = Map.Maps[1];
Assert.NotNull(map);
- var coords = new (int, int)[]
+ var coords = new[]
{
(1500, 1600), (1516, 1600), (1500, 1616), (1516, 1616), (1532, 1600)
};
@@ -323,7 +323,7 @@ public class StepCacheFileTests
cache.TryGetMask(map, x, y, sourceZ: 10);
}
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-preload-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-preload-{Guid.NewGuid():N}.swb");
try
{
Assert.Equal(coords.Length, cache.SaveToFile(path, map.MapID));
@@ -369,7 +369,7 @@ public class StepCacheFileTests
// Build + save one chunk.
cache.TryGetMask(map, 1500, 1600, sourceZ: 10);
- var path = Path.Combine(Path.GetTempPath(), $"step-cache-bypass-{System.Guid.NewGuid():N}.swb");
+ var path = Path.Combine(Path.GetTempPath(), $"step-cache-bypass-{Guid.NewGuid():N}.swb");
try
{
Assert.Equal(1, cache.SaveToFile(path, map.MapID));
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV6Tests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV6Tests.cs
index 30d8630c1..24764c272 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV6Tests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV6Tests.cs
@@ -48,8 +48,8 @@ public class StepCacheFileV6Tests
for (var i = 0; i < StepChunk.CellsPerChunk; i++)
{
c.WalkMask[i] = (byte)(i & 0xFF);
- c.WetMask[i] = (byte)((~i) & 0xFF);
- c.SourceZ[i] = (sbyte)(baseZ + (i % 7) - 3); // varies, mostly != 0
+ c.WetMask[i] = (byte)(~i & 0xFF);
+ c.SourceZ[i] = (sbyte)(baseZ + i % 7 - 3); // varies, mostly != 0
}
SetFlatDirectional(c);
return c;
@@ -78,9 +78,9 @@ public class StepCacheFileV6Tests
{
c.WalkMask[i] = (byte)(i & 0xFF);
c.WetMask[i] = (byte)((i * 7) & 0xFF);
- c.SourceZ[i] = (sbyte)((i % 40) - 20);
- c.WalkZN[i] = (sbyte)(c.SourceZ[i] + (i % 3));
- c.SwimZS[i] = (sbyte)(c.SourceZ[i] - (i % 2));
+ c.SourceZ[i] = (sbyte)(i % 40 - 20);
+ c.WalkZN[i] = (sbyte)(c.SourceZ[i] + i % 3);
+ c.SwimZS[i] = (sbyte)(c.SourceZ[i] - i % 2);
}
return c;
}
@@ -91,10 +91,10 @@ public class StepCacheFileV6Tests
c.AllocateSwimLayer();
for (var i = 0; i < StepChunk.CellsPerChunk; i++)
{
- c.SwimSourceZ[i] = (sbyte)((i % 30) - 15);
+ c.SwimSourceZ[i] = (sbyte)(i % 30 - 15);
c.SwimMask[i] = (byte)((i * 5) & 0xFF);
c.SwimZN_Layer[i] = (sbyte)(i % 7);
- c.SwimZNW_Layer[i] = (sbyte)(-(i % 4));
+ c.SwimZNW_Layer[i] = (sbyte)-(i % 4);
}
return c;
}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV7Tests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV7Tests.cs
index 83a44d46a..f5e517972 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV7Tests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV7Tests.cs
@@ -18,9 +18,9 @@ public class StepCacheFileV7Tests
{
c.WalkMask[i] = (byte)(i & 0xFF);
c.WetMask[i] = (byte)((i * 7) & 0xFF);
- c.SourceZ[i] = (sbyte)((i % 40) - 20);
- c.WalkZN[i] = (sbyte)(c.SourceZ[i] + (i % 3));
- c.SwimZS[i] = (sbyte)(c.SourceZ[i] - (i % 2));
+ c.SourceZ[i] = (sbyte)(i % 40 - 20);
+ c.WalkZN[i] = (sbyte)(c.SourceZ[i] + i % 3);
+ c.SwimZS[i] = (sbyte)(c.SourceZ[i] - i % 2);
}
return c;
}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV8Tests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV8Tests.cs
index 0a391fb39..8d3cd495b 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV8Tests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheFileV8Tests.cs
@@ -18,9 +18,9 @@ public class StepCacheFileV8Tests
{
c.WalkMask[i] = (byte)((i + seed) & 0xFF);
c.WetMask[i] = (byte)((i * 7 + seed) & 0xFF);
- c.SourceZ[i] = (sbyte)(((i + seed) % 40) - 20);
- c.WalkZN[i] = (sbyte)(c.SourceZ[i] + (i % 3));
- c.SwimZS[i] = (sbyte)(c.SourceZ[i] - (i % 2));
+ c.SourceZ[i] = (sbyte)((i + seed) % 40 - 20);
+ c.WalkZN[i] = (sbyte)(c.SourceZ[i] + i % 3);
+ c.SwimZS[i] = (sbyte)(c.SourceZ[i] - i % 2);
}
return c;
}
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
index de4b2fa51..72531ba97 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheLifecycleTests.cs
@@ -499,7 +499,7 @@ public class StepCacheLifecycleTests
// Build 5 distinct chunks by querying different sectors.
for (var i = 0; i < 5; i++)
{
- var x = 1500 + (i * 16);
+ var x = 1500 + i * 16;
var y = 1600;
cache.TryGetMask(map, x, y, 10);
System.Threading.Thread.Sleep(2); // ensure LastTouchedTicks differs
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
index b00d30f8f..149a9fe13 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepCacheParityTests.cs
@@ -83,10 +83,11 @@ public class StepCacheParityTests
wetCells++;
}
- if (lookup.WalkZ_N != baker.WalkZ_N || lookup.WalkZ_NE != baker.WalkZ_NE
- || lookup.WalkZ_E != baker.WalkZ_E || lookup.WalkZ_SE != baker.WalkZ_SE
- || lookup.WalkZ_S != baker.WalkZ_S || lookup.WalkZ_SW != baker.WalkZ_SW
- || lookup.WalkZ_W != baker.WalkZ_W || lookup.WalkZ_NW != baker.WalkZ_NW)
+ if (lookup.WalkZ_N != baker.WalkZ_N
+ || lookup.WalkZ_NE != baker.WalkZ_NE || lookup.WalkZ_E != baker.WalkZ_E
+ || lookup.WalkZ_SE != baker.WalkZ_SE || lookup.WalkZ_S != baker.WalkZ_S
+ || lookup.WalkZ_SW != baker.WalkZ_SW || lookup.WalkZ_W != baker.WalkZ_W
+ || lookup.WalkZ_NW != baker.WalkZ_NW)
{
disagreements++;
_output.WriteLine($"Z DIFF @ ({x},{y}) cache=({lookup.WalkZ_N},{lookup.WalkZ_NE},{lookup.WalkZ_E},{lookup.WalkZ_SE},{lookup.WalkZ_S},{lookup.WalkZ_SW},{lookup.WalkZ_W},{lookup.WalkZ_NW}) baker=({baker.WalkZ_N},{baker.WalkZ_NE},{baker.WalkZ_E},{baker.WalkZ_SE},{baker.WalkZ_S},{baker.WalkZ_SW},{baker.WalkZ_W},{baker.WalkZ_NW})");
diff --git a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
index 25b80773d..df8ff5da5 100644
--- a/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
+++ b/Projects/UOContent.Tests/Tests/Engines/Pathing/StepProbeParityTests.cs
@@ -52,7 +52,7 @@ public class StaticWalkabilityParityTests
// diagonal walkable iff raw-diagonal AND (left-partner OR right-partner).
// (Raw masks are correct per spec; baker omits diagonal logic per design.)
var newOk = bakerResult.IsWalkable(dir);
- if (newOk && ((d & 1) == 1))
+ if (newOk && (d & 1) == 1)
{
var leftPartner = (Direction)((d - 1) & 7);
var rightPartner = (Direction)((d + 1) & 7);
diff --git a/Projects/UOContent/Engines/Pathing/BitmapAStarAlgorithm.cs b/Projects/UOContent/Engines/Pathing/BitmapAStarAlgorithm.cs
index 564791ed5..db4aa35b8 100644
--- a/Projects/UOContent/Engines/Pathing/BitmapAStarAlgorithm.cs
+++ b/Projects/UOContent/Engines/Pathing/BitmapAStarAlgorithm.cs
@@ -23,7 +23,7 @@ using Server.Systems.FeatureFlags;
using CalcMoves = Server.Movement.Movement;
using MoveImpl = Server.Movement.MovementImpl;
-namespace Server.PathAlgorithms.BitmapAStar;
+namespace Server.PathAlgorithms;
///
/// A* pathfinder with a single bitmap-cache lookup per cell expansion. Default walkers
@@ -43,7 +43,6 @@ public class BitmapAStarAlgorithm : PathAlgorithm
public int z;
}
- private const int MaxDepth = 300;
private const int AreaSize = 38;
private const int NodeCount = AreaSize * AreaSize * PlaneCount;
@@ -51,38 +50,66 @@ public class BitmapAStarAlgorithm : PathAlgorithm
private const int PlaneOffset = 128;
private const int PlaneCount = 13;
private const int PlaneHeight = 20;
- public static readonly PathAlgorithm Instance = new BitmapAStarAlgorithm();
+ // Default shared singleton (MaxSearchNodes = 1000, set from config in Configure). Typed
+ // as the concrete class so Configure can set its instance config; assignable anywhere a
+ // PathAlgorithm is expected. Specialized variants are just additional instances.
+ public static readonly BitmapAStarAlgorithm Instance = new();
- private static readonly Direction[] _path = new Direction[AreaSize * AreaSize];
- private static readonly PathNode[] _nodes = new PathNode[NodeCount];
- private static readonly byte[] _nodeStates = new byte[NodeCount];
- private static readonly int[] _successors = new int[8];
- private static readonly PriorityQueue _openQueue = new();
+ // Scratch buffers — reused across every Find on THIS instance. Per-instance (not static)
+ // so independently-configured algorithms don't share state. ~320 KB per instance; create
+ // specialized instances once (static readonly), never per-call. Safe to reuse per Find
+ // because the game loop is single-threaded and Find is never re-entered.
+ private readonly Direction[] _path = new Direction[AreaSize * AreaSize];
+ private readonly PathNode[] _nodes = new PathNode[NodeCount];
+ private readonly byte[] _nodeStates = new byte[NodeCount];
+ private readonly int[] _successors = new int[8];
+ private readonly PriorityQueue _openQueue = new();
- private static int _xOffset;
- private static int _yOffset;
+ // A* node-expansion budget: the search bails (returning null) after this many node
+ // expansions. Benchmarked as near-optimal: above the ~500 needed to solve walled-off
+ // indoor routes, below the ~1500 window-exhaustion cost ceiling where a failed
+ // (unreachable) search's worst-case cost spikes for no solving benefit. Successful
+ // searches terminate on goal-found, so this never touches the common open-terrain case.
+ // Per-instance so specialized algorithms (e.g. a wider-budget variant for special NPCs)
+ // can coexist; the shared default lives on Instance and is set from config in Configure.
+ public int MaxSearchNodes { get; set; } = 1000;
+
+ private int _xOffset;
+ private int _yOffset;
// When set, GetSuccessors delegates to the per-cell slow path on every expansion
// (creature has CanFly — Z-jumping is beyond the cache's static-only scope).
- private static bool _currentMobileNeedsSlowPath;
+ private bool _currentMobileNeedsSlowPath;
// When set, diagonal corner-cut uses the strict AND-rule (BOTH cardinal partners
// must be walkable) instead of the lenient creature OR-rule. Cache still applies —
// partner bits live in the same source-cell mask byte. Non-GM players only.
- private static bool _currentMobilePlayerStrict;
+ private bool _currentMobilePlayerStrict;
// Capability overlay applied to cache results. Layered each cell:
// effective = (walkMask & !cantWalk) | (wetMask & canSwim)
// Reset at end of Find.
- private static bool _currentMobileCanSwim;
- private static bool _currentMobileCantWalk;
+ private bool _currentMobileCanSwim;
+ private bool _currentMobileCantWalk;
// Dynamic-obstacle pass capability flags (per-mobile, captured in Find).
// Mirrors MovementImpl.Check's per-mobile derivations so per-cell items/mobiles
// checks can be evaluated without re-deriving.
- private static bool _currentMobileIgnoreDoors;
- private static bool _currentMobileIgnoreSpellFields;
- private static bool _currentMobileIgnoreMovableImpassables;
+ private bool _currentMobileIgnoreDoors;
+ private bool _currentMobileIgnoreSpellFields;
+ private bool _currentMobileIgnoreMovableImpassables;
+
+ public static void Configure()
+ {
+ // A* node-expansion budget. Default 1000 is benchmarked near-optimal (see
+ // MaxSearchNodes). Applied to the shared singleton; specialized instances pass their
+ // own value. Written back to server.cfg on first boot. Auto-invoked at startup via
+ // AssemblyHandler.Invoke("Configure").
+ Instance.MaxSearchNodes = ServerConfiguration.GetOrUpdateSetting(
+ "pathfinding.maxSearchNodes",
+ 1000
+ );
+ }
private Point3D _goal;
@@ -133,6 +160,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
_currentMobileIgnoreDoors = false;
_currentMobileIgnoreMovableImpassables = false;
}
+
// Mirrors MovementImpl: dead/spectral mobiles also ignore doors.
_currentMobileIgnoreDoors |= !m.Alive || m.Body.BodyID == 0x3DB || m.IsDeadBondedPet;
_currentMobileIgnoreSpellFields = m is PlayerMobile && map != Map.Felucca;
@@ -163,7 +191,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
while (_openQueue.Count > 0)
{
- if (++depth > MaxDepth)
+ if (++depth > MaxSearchNodes)
{
break;
}
@@ -287,7 +315,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
return null;
}
- private static int GetIndex(int x, int y, int z)
+ private int GetIndex(int x, int y, int z)
{
x -= _xOffset;
y -= _yOffset;
@@ -304,7 +332,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
/// On cache fallthrough or for non-default walkers, defers to
/// for THIS cell only.
///
- private static int GetSuccessors(int p, Mobile m, Map map)
+ private int GetSuccessors(int p, Mobile m, Map map)
{
var px = p % AreaSize;
var py = p / AreaSize % AreaSize;
@@ -427,7 +455,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
/// non-Felucca players → ignore spell fields). Mobiles: any other mobile whose Z range
/// overlaps and which we can't move over.
///
- private static bool IsBlockedByDynamic(Mobile m, Map map, int x, int y, int z)
+ private bool IsBlockedByDynamic(Mobile m, Map map, int x, int y, int z)
{
var ourTop = z + PersonHeightConst;
@@ -504,7 +532,7 @@ public class BitmapAStarAlgorithm : PathAlgorithm
/// CheckMovement validates land/statics/items via MovementImpl; dynamic mobile blocking
/// is layered on top because MovementImpl doesn't iterate same-cell mobiles.
///
- private static int GetSuccessorsSlowPath(Mobile m, Map map, int px, int py, Point3D p3D, int[] vals)
+ private int GetSuccessorsSlowPath(Mobile m, Map map, int px, int py, Point3D p3D, int[] vals)
{
var count = 0;
diff --git a/Projects/UOContent/Engines/Pathing/Cache/StepCache.cs b/Projects/UOContent/Engines/Pathing/Cache/StepCache.cs
index 2be027477..055e19173 100644
--- a/Projects/UOContent/Engines/Pathing/Cache/StepCache.cs
+++ b/Projects/UOContent/Engines/Pathing/Cache/StepCache.cs
@@ -460,7 +460,27 @@ public sealed class StepCache
if (map == null || map == Map.Internal || x < 0 || y < 0 || x >= map.Width || y >= map.Height)
{
_fallthroughOffMap++;
- return new StepMask(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CacheHitKind.Fallthrough_OffMap);
+ return new StepMask(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ CacheHitKind.Fallthrough_OffMap
+ );
}
var chunkX = x >> 4;
@@ -489,7 +509,27 @@ public sealed class StepCache
else
{
_fallthroughNotBuilt++;
- return new StepMask(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CacheHitKind.Fallthrough_NotBuilt);
+ return new StepMask(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ CacheHitKind.Fallthrough_NotBuilt
+ );
}
}
else
@@ -524,7 +564,27 @@ public sealed class StepCache
return stratumResult;
}
_fallthroughMultiZ++;
- return new StepMask(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CacheHitKind.Fallthrough_MultiZ);
+ return new StepMask(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ CacheHitKind.Fallthrough_MultiZ
+ );
}
// Source-Z guard: the cache stores one answer per cell baked at SourceZ.
@@ -564,7 +624,27 @@ public sealed class StepCache
}
_fallthroughSourceZMismatch++;
- return new StepMask(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CacheHitKind.Fallthrough_SourceZMismatch);
+ return new StepMask(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ CacheHitKind.Fallthrough_SourceZMismatch
+ );
}
switch (hitKindResult)
diff --git a/Projects/UOContent/Engines/Pathing/Cache/StepCacheFile.cs b/Projects/UOContent/Engines/Pathing/Cache/StepCacheFile.cs
index d737e6b52..85afd68e4 100644
--- a/Projects/UOContent/Engines/Pathing/Cache/StepCacheFile.cs
+++ b/Projects/UOContent/Engines/Pathing/Cache/StepCacheFile.cs
@@ -234,9 +234,7 @@ internal static class StepCacheFile
// chunks add another ~2.5 KB (swim layer) but they're a small fraction of any
// map; the writer grows on overflow so under-estimating just causes a few
// realloc/copy cycles during the bake — not a correctness issue.
- var capacity = HeaderSize
- + (BytesPerChunkBase + 256) * (int)chunkCount
- + IndexEntryBytes * (int)chunkCount;
+ var capacity = HeaderSize + (BytesPerChunkBase + 256) * (int)chunkCount + IndexEntryBytes * (int)chunkCount;
var buffer = new byte[capacity];
var w = new BufferWriter(buffer, prefixStr: false);
diff --git a/Projects/UOContent/Engines/Pathing/MovementPath.cs b/Projects/UOContent/Engines/Pathing/MovementPath.cs
index eb5d2f9d4..b5307ad3d 100644
--- a/Projects/UOContent/Engines/Pathing/MovementPath.cs
+++ b/Projects/UOContent/Engines/Pathing/MovementPath.cs
@@ -4,7 +4,6 @@ using Server.Engines.Pathing;
using Server.Engines.Pathing.Cache;
using Server.Items;
using Server.PathAlgorithms;
-using Server.PathAlgorithms.BitmapAStar;
using Server.Spells;
using Server.Targeting;
diff --git a/Projects/UOContent/Engines/Pathing/PathDiag.cs b/Projects/UOContent/Engines/Pathing/PathDiag.cs
index 51b1372b8..d115c3c6c 100644
--- a/Projects/UOContent/Engines/Pathing/PathDiag.cs
+++ b/Projects/UOContent/Engines/Pathing/PathDiag.cs
@@ -2,7 +2,7 @@ using System;
using System.Diagnostics;
using System.IO;
using Server.Engines.Pathing.Cache;
-using Server.PathAlgorithms.BitmapAStar;
+using Server.PathAlgorithms;
using Server.Targeting;
namespace Server.Engines.Pathing;
diff --git a/Projects/UOContent/Engines/Pathing/PathfindRecorder.cs b/Projects/UOContent/Engines/Pathing/PathfindRecorder.cs
index 795719911..f21637dc4 100644
--- a/Projects/UOContent/Engines/Pathing/PathfindRecorder.cs
+++ b/Projects/UOContent/Engines/Pathing/PathfindRecorder.cs
@@ -31,18 +31,15 @@ public static class PathfindRecorder
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(PathfindRecorder));
- private static bool _enabled;
- private static string _outputPath;
private static StreamWriter _writer;
- private static long _recordsWritten;
- public static bool Enabled => _enabled;
- public static string OutputPath => _outputPath;
- public static long RecordsWritten => _recordsWritten;
+ public static bool Enabled { get; private set; }
+ public static string OutputPath { get; set; }
+ public static long RecordsWritten { get; private set; }
public static void Configure()
{
- _outputPath = ServerConfiguration.GetOrUpdateSetting(
+ OutputPath = ServerConfiguration.GetOrUpdateSetting(
"pathfinding.recorder.path",
Path.Combine(Core.BaseDirectory, "Data", "Pathfinding", "recordings", "pathfinds.jsonl")
);
@@ -61,7 +58,7 @@ public static class PathfindRecorder
///
public static void SetEnabled(bool enabled)
{
- if (enabled == _enabled)
+ if (enabled == Enabled)
{
return;
}
@@ -70,22 +67,22 @@ public static class PathfindRecorder
{
try
{
- Directory.CreateDirectory(Path.GetDirectoryName(_outputPath) ?? ".");
- var stream = new FileStream(_outputPath, FileMode.Append, FileAccess.Write, FileShare.Read);
+ Directory.CreateDirectory(Path.GetDirectoryName(OutputPath) ?? ".");
+ var stream = new FileStream(OutputPath, FileMode.Append, FileAccess.Write, FileShare.Read);
_writer = new StreamWriter(stream, new UTF8Encoding(false));
- _enabled = true;
- logger.Information("PathfindRecorder enabled, writing to {Path}", _outputPath);
+ Enabled = true;
+ logger.Information("PathfindRecorder enabled, writing to {Path}", OutputPath);
}
catch (IOException ex)
{
- logger.Warning(ex, "PathfindRecorder: failed to open {Path} for write", _outputPath);
+ logger.Warning(ex, "PathfindRecorder: failed to open {Path} for write", OutputPath);
_writer = null;
- _enabled = false;
+ Enabled = false;
}
}
else
{
- _enabled = false;
+ Enabled = false;
try
{
_writer?.Flush();
@@ -93,10 +90,10 @@ public static class PathfindRecorder
}
catch (IOException ex)
{
- logger.Warning(ex, "PathfindRecorder: error closing {Path}", _outputPath);
+ logger.Warning(ex, "PathfindRecorder: error closing {Path}", OutputPath);
}
_writer = null;
- logger.Information("PathfindRecorder disabled ({Count} records this session)", _recordsWritten);
+ logger.Information("PathfindRecorder disabled ({Count} records this session)", RecordsWritten);
}
}
@@ -113,7 +110,7 @@ public static class PathfindRecorder
}
catch (IOException ex)
{
- logger.Warning(ex, "PathfindRecorder: flush failed for {Path}", _outputPath);
+ logger.Warning(ex, "PathfindRecorder: flush failed for {Path}", OutputPath);
}
}
@@ -124,7 +121,7 @@ public static class PathfindRecorder
///
public static void RecordIfEnabled(Mobile m, Map map, Point3D start, Point3D goal)
{
- if (!_enabled || _writer == null || m == null || map == null)
+ if (!Enabled || _writer == null || m == null || map == null)
{
return;
}
@@ -159,7 +156,7 @@ public static class PathfindRecorder
vsb.Append(canMoveOverObstacles ? "true" : "false");
vsb.Append("}\n");
_writer.Write(vsb.AsSpan());
- _recordsWritten++;
+ RecordsWritten++;
}
catch (IOException ex)
{