muffin v7.5.13
This commit is contained in:
parent
911ed68baa
commit
acf3e3cb18
69 changed files with 2731 additions and 1425 deletions
188
SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs
Normal file
188
SmartNav.Model/SmartNav.Model.Navigation/TerritoryWaterMap.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace SmartNav.Model.Navigation;
|
||||
|
||||
public sealed class TerritoryWaterMap
|
||||
{
|
||||
public const int GridSize = 256;
|
||||
|
||||
public const int CellCount = 65536;
|
||||
|
||||
public const float CellSize = 8f;
|
||||
|
||||
public const float WorldMin = -1024f;
|
||||
|
||||
public const float WorldMax = 1024f;
|
||||
|
||||
private const float SurfaceYScale = 32f;
|
||||
|
||||
private const float SurfaceYBias = 1024f;
|
||||
|
||||
private readonly byte[] _classes;
|
||||
|
||||
private readonly ushort[] _surfaceY;
|
||||
|
||||
public ushort TerritoryId { get; }
|
||||
|
||||
public ReadOnlySpan<byte> Classes => _classes;
|
||||
|
||||
public ReadOnlySpan<ushort> SurfaceYRaw => _surfaceY;
|
||||
|
||||
public bool HasWater => ((ReadOnlySpan<byte>)_classes.AsSpan()).ContainsAnyExcept((byte)0);
|
||||
|
||||
public TerritoryWaterMap(ushort territoryId)
|
||||
: this(territoryId, new byte[65536], new ushort[65536])
|
||||
{
|
||||
}
|
||||
|
||||
public TerritoryWaterMap(ushort territoryId, byte[] classes, ushort[] surfaceY)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(classes, "classes");
|
||||
ArgumentNullException.ThrowIfNull(surfaceY, "surfaceY");
|
||||
if (classes.Length != 65536)
|
||||
{
|
||||
throw new ArgumentException($"expected {65536} class entries, got {classes.Length}", "classes");
|
||||
}
|
||||
if (surfaceY.Length != 65536)
|
||||
{
|
||||
throw new ArgumentException($"expected {65536} surface Y entries, got {surfaceY.Length}", "surfaceY");
|
||||
}
|
||||
TerritoryId = territoryId;
|
||||
_classes = classes;
|
||||
_surfaceY = surfaceY;
|
||||
}
|
||||
|
||||
public static ushort EncodeSurfaceY(float y)
|
||||
{
|
||||
if (float.IsNaN(y))
|
||||
{
|
||||
throw new ArgumentException("surface Y is NaN", "y");
|
||||
}
|
||||
float num = MathF.Round((y + 1024f) * 32f);
|
||||
if (num <= 0f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (num >= 65535f)
|
||||
{
|
||||
return ushort.MaxValue;
|
||||
}
|
||||
return (ushort)num;
|
||||
}
|
||||
|
||||
public static float DecodeSurfaceY(ushort raw)
|
||||
{
|
||||
return (float)(int)raw / 32f - 1024f;
|
||||
}
|
||||
|
||||
public static float CellCenterCoordinate(int colOrRow)
|
||||
{
|
||||
return -1024f + ((float)colOrRow + 0.5f) * 8f;
|
||||
}
|
||||
|
||||
public static bool TryGetCell(float x, float z, out int col, out int row)
|
||||
{
|
||||
col = (int)MathF.Floor((x - -1024f) / 8f);
|
||||
row = (int)MathF.Floor((z - -1024f) / 8f);
|
||||
if (col < 0 || col >= 256 || row < 0 || row >= 256)
|
||||
{
|
||||
col = -1;
|
||||
row = -1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCell(int col, int row, WaterCellClass cellClass, float surfaceY)
|
||||
{
|
||||
int num = IndexOf(col, row);
|
||||
_classes[num] = (byte)cellClass;
|
||||
_surfaceY[num] = EncodeSurfaceY(surfaceY);
|
||||
}
|
||||
|
||||
public WaterCellClass GetCell(int col, int row)
|
||||
{
|
||||
return (WaterCellClass)_classes[IndexOf(col, row)];
|
||||
}
|
||||
|
||||
public WaterCellClass ClassifyAt(float x, float z)
|
||||
{
|
||||
if (!TryGetCell(x, z, out var col, out var row))
|
||||
{
|
||||
return WaterCellClass.None;
|
||||
}
|
||||
return (WaterCellClass)_classes[row * 256 + col];
|
||||
}
|
||||
|
||||
public bool TryGetSurfaceY(float x, float z, out float surfaceY)
|
||||
{
|
||||
surfaceY = 0f;
|
||||
if (!TryGetCell(x, z, out var col, out var row))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num = row * 256 + col;
|
||||
if (_classes[num] == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
surfaceY = DecodeSurfaceY(_surfaceY[num]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryFindNearestDivable(float x, float z, float maxRadiusYalms, out Vector3 entryCellCenter)
|
||||
{
|
||||
entryCellCenter = default(Vector3);
|
||||
if (float.IsNaN(maxRadiusYalms) || maxRadiusYalms < 0f || !TryGetCell(x, z, out var col, out var row))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
float num = MathF.Ceiling(maxRadiusYalms / 8f) + 1f;
|
||||
int num2 = ((num >= 256f) ? 256 : ((int)num));
|
||||
float num3 = float.MaxValue;
|
||||
int num4 = -1;
|
||||
int num5 = -1;
|
||||
for (int i = 0; i <= num2 && (num4 < 0 || !(((float)i - 0.5f) * 8f > num3)); i++)
|
||||
{
|
||||
int num6 = col - i;
|
||||
int num7 = col + i;
|
||||
int num8 = row - i;
|
||||
int num9 = row + i;
|
||||
for (int j = Math.Max(num8, 0); j <= Math.Min(num9, 255); j++)
|
||||
{
|
||||
int num10 = ((j == num8 || j == num9) ? 1 : Math.Max(num7 - num6, 1));
|
||||
for (int k = num6; k <= num7; k += num10)
|
||||
{
|
||||
if (k >= 0 && k < 256 && _classes[j * 256 + k] == 2)
|
||||
{
|
||||
float num11 = CellCenterCoordinate(k) - x;
|
||||
float num12 = CellCenterCoordinate(j) - z;
|
||||
float num13 = MathF.Sqrt(num11 * num11 + num12 * num12);
|
||||
if (!(num13 > maxRadiusYalms) && !(num13 >= num3))
|
||||
{
|
||||
num3 = num13;
|
||||
num4 = k;
|
||||
num5 = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num4 < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
entryCellCenter = new Vector3(CellCenterCoordinate(num4), DecodeSurfaceY(_surfaceY[num5 * 256 + num4]), CellCenterCoordinate(num5));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int IndexOf(int col, int row)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(col, "col");
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(col, 256, "col");
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(row, "row");
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(row, 256, "row");
|
||||
return row * 256 + col;
|
||||
}
|
||||
}
|
||||
122
SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs
Normal file
122
SmartNav.Model/SmartNav.Model.Navigation/WaterCacheFormat.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace SmartNav.Model.Navigation;
|
||||
|
||||
public static class WaterCacheFormat
|
||||
{
|
||||
public const uint FileMagic = 1129469015u;
|
||||
|
||||
public const int FileFormatVersion = 1;
|
||||
|
||||
public const string FileName = "water-cache.bin";
|
||||
|
||||
private const int MaxTerritoryCount = 65536;
|
||||
|
||||
private const int ClassBytes = 65536;
|
||||
|
||||
private const int SurfaceBytes = 131072;
|
||||
|
||||
private const int PayloadBytes = 196608;
|
||||
|
||||
public static void Write(Stream stream, string gameVersion, IReadOnlyList<TerritoryWaterMap> maps)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream, "stream");
|
||||
ArgumentNullException.ThrowIfNull(gameVersion, "gameVersion");
|
||||
ArgumentNullException.ThrowIfNull(maps, "maps");
|
||||
List<TerritoryWaterMap> list = new List<TerritoryWaterMap>(maps.Count);
|
||||
foreach (TerritoryWaterMap map in maps)
|
||||
{
|
||||
if (map.HasWater)
|
||||
{
|
||||
list.Add(map);
|
||||
}
|
||||
}
|
||||
list.Sort((TerritoryWaterMap left, TerritoryWaterMap right) => left.TerritoryId.CompareTo(right.TerritoryId));
|
||||
using BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
binaryWriter.Write(1129469015u);
|
||||
binaryWriter.Write(1);
|
||||
binaryWriter.Write(gameVersion);
|
||||
binaryWriter.Write(list.Count);
|
||||
foreach (TerritoryWaterMap item in list)
|
||||
{
|
||||
byte[] array = new byte[196608];
|
||||
item.Classes.CopyTo(array.AsSpan(0, 65536));
|
||||
ReadOnlySpan<ushort> surfaceYRaw = item.SurfaceYRaw;
|
||||
for (int num = 0; num < surfaceYRaw.Length; num++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(array.AsSpan(65536 + num * 2), surfaceYRaw[num]);
|
||||
}
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
|
||||
{
|
||||
deflateStream.Write(array);
|
||||
}
|
||||
binaryWriter.Write(item.TerritoryId);
|
||||
binaryWriter.Write((int)memoryStream.Length);
|
||||
binaryWriter.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public static (string GameVersion, List<TerritoryWaterMap> Maps) Read(Stream stream)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream, "stream");
|
||||
using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
|
||||
uint num = binaryReader.ReadUInt32();
|
||||
if (num != 1129469015)
|
||||
{
|
||||
throw new InvalidDataException($"water cache has magic 0x{num:X8}, expected 0x{1129469015:X8}");
|
||||
}
|
||||
int num2 = binaryReader.ReadInt32();
|
||||
if (num2 != 1)
|
||||
{
|
||||
throw new InvalidDataException($"water cache has format version {num2}, expected {1}");
|
||||
}
|
||||
string item = binaryReader.ReadString();
|
||||
int num3 = binaryReader.ReadInt32();
|
||||
if (num3 < 0 || num3 > 65536)
|
||||
{
|
||||
throw new InvalidDataException($"water cache declares {num3} territories");
|
||||
}
|
||||
List<TerritoryWaterMap> list = new List<TerritoryWaterMap>(num3);
|
||||
for (int i = 0; i < num3; i++)
|
||||
{
|
||||
ushort num4 = binaryReader.ReadUInt16();
|
||||
int num5 = binaryReader.ReadInt32();
|
||||
if (num5 < 0)
|
||||
{
|
||||
throw new InvalidDataException($"territory {num4} declares {num5} compressed bytes");
|
||||
}
|
||||
byte[] array = binaryReader.ReadBytes(num5);
|
||||
if (array.Length != num5)
|
||||
{
|
||||
throw new InvalidDataException($"territory {num4} is truncated: {array.Length} of {num5} bytes");
|
||||
}
|
||||
byte[] array2 = new byte[196608];
|
||||
using (DeflateStream deflateStream = new DeflateStream(new MemoryStream(array, writable: false), CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.ReadExactly(array2);
|
||||
}
|
||||
byte[] subArray = array2[..65536];
|
||||
byte[] array3 = subArray;
|
||||
foreach (byte b in array3)
|
||||
{
|
||||
if (b > 2)
|
||||
{
|
||||
throw new InvalidDataException($"territory {num4} has cell class {b}, expected 0..{2}");
|
||||
}
|
||||
}
|
||||
ushort[] array4 = new ushort[65536];
|
||||
for (int k = 0; k < array4.Length; k++)
|
||||
{
|
||||
array4[k] = BinaryPrimitives.ReadUInt16LittleEndian(array2.AsSpan(65536 + k * 2));
|
||||
}
|
||||
list.Add(new TerritoryWaterMap(num4, subArray, array4));
|
||||
}
|
||||
return (GameVersion: item, Maps: list);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace SmartNav.Model.Navigation;
|
||||
|
||||
public enum WaterCellClass : byte
|
||||
{
|
||||
None,
|
||||
SwimOnly,
|
||||
Divable
|
||||
}
|
||||
|
|
@ -56,9 +56,25 @@ public static class MovementData
|
|||
|
||||
public const float DefaultMountGroundDistance = 20f;
|
||||
|
||||
public const float MinMountTimeSavedSeconds = 2f;
|
||||
|
||||
public const float DefaultMountFlyDistance = 15f;
|
||||
|
||||
public const float DefaultStopDistance = 3f;
|
||||
|
||||
public static float MountGroundDistanceFor(int mountSpeedLevel)
|
||||
{
|
||||
return 2f / (1f / 6f - 1f / Speeds.GetMountGroundSpeed(mountSpeedLevel));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Water
|
||||
{
|
||||
public const float EjectDepth = 20.6f;
|
||||
|
||||
public const float EntryDepth = 25f;
|
||||
|
||||
public const float SwimDepth = 0.6f;
|
||||
}
|
||||
|
||||
public static class Timings
|
||||
|
|
@ -74,5 +90,9 @@ public static class MovementData
|
|||
public const float SprintDurationInCombat = 10f;
|
||||
|
||||
public const float SprintCooldown = 60f;
|
||||
|
||||
public const float DiveEntryDuration = 4f;
|
||||
|
||||
public const float SurfaceExitDuration = 1f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue