using System; namespace Server.Engines.Pathing.Cache; /// /// Per-chunk storage backing StepCache. Holds raw walk + swim masks and destination Z /// values for each of 256 cells in a 16x16 chunk, plus build-time metadata (multis /// version, multi-Z strata) and LRU bookkeeping. /// internal sealed class StepChunk { public const int CellsPerChunk = 256; // 16 x 16 /// Bit i of WalkMask[c] = "default walker can step from cell c to neighbor (Direction)i". Raw — no diagonal corner-cut applied here. public readonly byte[] WalkMask = new byte[CellsPerChunk]; /// Bit i of WetMask[c] = "swim-only mob can step from cell c to neighbor (Direction)i". Layered with WalkMask via canSwim/cantWalk capability flags. public readonly byte[] WetMask = new byte[CellsPerChunk]; public readonly sbyte[] SourceZ = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZN = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZNE = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZE = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZSE = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZS = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZSW = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZW = new sbyte[CellsPerChunk]; public readonly sbyte[] WalkZNW = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZN = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZNE = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZE = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZSE = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZS = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZSW = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZW = new sbyte[CellsPerChunk]; public readonly sbyte[] SwimZNW = new sbyte[CellsPerChunk]; /// /// Swim layer — populated only for chunks containing at least one shore cell (a cell /// with both a walkable land surface and a water surface separated by > StepHeight). /// On shore cells, queries from the swim source Z miss the primary source-Z guard; /// the swim layer carries the correct wetMask + per-direction destination Zs computed /// from the water surface's perspective. For non-shore cells in a chunk that has the /// layer, [cell] = sentinel. /// All swim-layer arrays are null on chunks with no shore cells (~90% of map chunks /// on Trammel) — zero memory cost on the common case. /// public const sbyte NoSwimLayerCell = sbyte.MinValue; private sbyte[] _swimSourceZ; private byte[] _swimMask; private sbyte[] _swimZN_extra; private sbyte[] _swimZNE_extra; private sbyte[] _swimZE_extra; private sbyte[] _swimZSE_extra; private sbyte[] _swimZS_extra; private sbyte[] _swimZSW_extra; private sbyte[] _swimZW_extra; private sbyte[] _swimZNW_extra; /// True when this chunk has at least one shore cell with a populated swim layer. public bool HasSwimLayer => _swimSourceZ != null; /// Per-cell water-surface standing Z (or ). Null when chunk has no swim layer. public sbyte[] SwimSourceZ => _swimSourceZ; /// Per-cell swim mask computed at . Null when chunk has no swim layer. public byte[] SwimMask => _swimMask; public sbyte[] SwimZN_Layer => _swimZN_extra; public sbyte[] SwimZNE_Layer => _swimZNE_extra; public sbyte[] SwimZE_Layer => _swimZE_extra; public sbyte[] SwimZSE_Layer => _swimZSE_extra; public sbyte[] SwimZS_Layer => _swimZS_extra; public sbyte[] SwimZSW_Layer => _swimZSW_extra; public sbyte[] SwimZW_Layer => _swimZW_extra; public sbyte[] SwimZNW_Layer => _swimZNW_extra; /// /// Lazily allocates the swim-layer arrays and seeds with /// the sentinel. Called at bake time the first time a /// shore cell is detected in this chunk. /// internal void AllocateSwimLayer() { if (_swimSourceZ != null) { return; } _swimSourceZ = new sbyte[CellsPerChunk]; _swimMask = new byte[CellsPerChunk]; _swimZN_extra = new sbyte[CellsPerChunk]; _swimZNE_extra = new sbyte[CellsPerChunk]; _swimZE_extra = new sbyte[CellsPerChunk]; _swimZSE_extra = new sbyte[CellsPerChunk]; _swimZS_extra = new sbyte[CellsPerChunk]; _swimZSW_extra = new sbyte[CellsPerChunk]; _swimZW_extra = new sbyte[CellsPerChunk]; _swimZNW_extra = new sbyte[CellsPerChunk]; for (var i = 0; i < CellsPerChunk; i++) { _swimSourceZ[i] = NoSwimLayerCell; } } /// Test/serialization hook: install pre-built swim-layer arrays. Pass nulls to clear. internal void SetSwimLayer( sbyte[] swimSourceZ, byte[] swimMask, sbyte[] zN, sbyte[] zNE, sbyte[] zE, sbyte[] zSE, sbyte[] zS, sbyte[] zSW, sbyte[] zW, sbyte[] zNW ) { _swimSourceZ = swimSourceZ; _swimMask = swimMask; _swimZN_extra = zN; _swimZNE_extra = zNE; _swimZE_extra = zE; _swimZSE_extra = zSE; _swimZS_extra = zS; _swimZSW_extra = zSW; _swimZW_extra = zW; _swimZNW_extra = zNW; } /// Sentinel: cell has no strata — single-Z, use the main Walk/Wet arrays. public const ushort NoStrata = ushort.MaxValue; /// /// Length-256 offset table: StrataOffsetByCell[cell] = byte offset into /// where this cell's strata begin, or /// for cells without multi-Z. Null when the chunk has zero multi-Z cells. /// private ushort[] _strataOffsetByCell; /// /// Packed per-cell strata. For each cell with strata: /// u8 stratumCount, then stratumCount × Stratum (19 bytes each): /// sbyte zCenter, byte walkMask, byte wetMask, /// sbyte walkZ_N..NW (8), sbyte swimZ_N..NW (8) /// private byte[] _strataData; /// Snapshot of Sector.MultisVersion at the time this chunk was built. public int BuiltMultisVersion; /// Updated on every cache hit/miss. Used by LRU fallback eviction. public long LastTouchedTicks; /// Size in bytes of one Stratum record in StrataData. public const int StratumByteLength = 1 + 1 + 1 + 8 + 8; public bool IsCellMultiZ(int cellIndex) => GetStrataOffset(cellIndex) != NoStrata; public ushort GetStrataOffset(int cellIndex) => _strataOffsetByCell == null ? NoStrata : _strataOffsetByCell[cellIndex]; public ReadOnlySpan StrataData => _strataData == null ? ReadOnlySpan.Empty : _strataData.AsSpan(); /// /// Single-shot setter for the chunk's strata. Pass null/null to clear (chunk becomes /// "no multi-Z"). Otherwise must be length 256 with /// for cells without strata, and the /// packed strata records. /// internal void SetStrata(ushort[] offsetByCell, byte[] data) { _strataOffsetByCell = offsetByCell; _strataData = data; } /// Serialization hook: returns the raw offset array (or null if no strata). internal ushort[] GetStrataOffsetByCellForSerialization() => _strataOffsetByCell; /// Serialization hook: returns the raw data array (or null if no strata). internal byte[] GetStrataDataForSerialization() => _strataData; }