test
This commit is contained in:
parent
0df70abad7
commit
0c14d78d8a
25 changed files with 1487 additions and 179 deletions
176
src/Roboto.Memory/Objects/Terrain.cs
Normal file
176
src/Roboto.Memory/Objects/Terrain.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using Roboto.Memory;
|
||||
using Serilog;
|
||||
using TerrainStruct = Roboto.GameOffsets.States.Terrain;
|
||||
|
||||
namespace Roboto.Memory.Objects;
|
||||
|
||||
/// <summary>
|
||||
/// Reads terrain walkability grid from AreaInstance.
|
||||
/// RemoteObject wrapping the previous TerrainReader logic.
|
||||
/// Update(areaInstanceAddr) to read terrain.
|
||||
/// </summary>
|
||||
public sealed class Terrain : RemoteObject
|
||||
{
|
||||
private uint _cachedTerrainAreaHash;
|
||||
private WalkabilityGrid? _cachedTerrain;
|
||||
private bool _wasLoading;
|
||||
|
||||
public WalkabilityGrid? Grid { get; private set; }
|
||||
public int TerrainWidth { get; private set; }
|
||||
public int TerrainHeight { get; private set; }
|
||||
public int TerrainCols { get; private set; }
|
||||
public int TerrainRows { get; private set; }
|
||||
public int WalkablePercent { get; private set; }
|
||||
|
||||
/// <summary>Set before Update() to indicate current loading/area state.</summary>
|
||||
public bool IsLoading { get; set; }
|
||||
public uint AreaHash { get; set; }
|
||||
|
||||
public Terrain(MemoryContext ctx) : base(ctx) { }
|
||||
|
||||
public void InvalidateCache()
|
||||
{
|
||||
_cachedTerrain = null;
|
||||
_cachedTerrainAreaHash = 0;
|
||||
}
|
||||
|
||||
protected override bool ReadData()
|
||||
{
|
||||
var mem = Ctx.Memory;
|
||||
var offsets = Ctx.Offsets;
|
||||
|
||||
if (!offsets.TerrainInline)
|
||||
{
|
||||
var terrainListPtr = mem.ReadPointer(Address + offsets.TerrainListOffset);
|
||||
if (terrainListPtr == 0) return true;
|
||||
|
||||
var terrainPtr = mem.ReadPointer(terrainListPtr);
|
||||
if (terrainPtr == 0) return true;
|
||||
|
||||
var dimsPtr = mem.ReadPointer(terrainPtr + offsets.TerrainDimensionsOffset);
|
||||
if (dimsPtr == 0) return true;
|
||||
|
||||
TerrainCols = mem.Read<int>(dimsPtr);
|
||||
TerrainRows = mem.Read<int>(dimsPtr + 4);
|
||||
if (TerrainCols > 0 && TerrainCols < 1000 &&
|
||||
TerrainRows > 0 && TerrainRows < 1000)
|
||||
{
|
||||
TerrainWidth = TerrainCols * offsets.SubTilesPerCell;
|
||||
TerrainHeight = TerrainRows * offsets.SubTilesPerCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
TerrainCols = 0;
|
||||
TerrainRows = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inline mode
|
||||
var terrainBase = Address + offsets.TerrainListOffset;
|
||||
var t = mem.Read<TerrainStruct>(terrainBase);
|
||||
|
||||
var cols = (int)t.Dimensions.X;
|
||||
var rows = (int)t.Dimensions.Y;
|
||||
|
||||
if (cols <= 0 || cols >= 1000 || rows <= 0 || rows >= 1000)
|
||||
return true;
|
||||
|
||||
TerrainCols = cols;
|
||||
TerrainRows = rows;
|
||||
TerrainWidth = cols * offsets.SubTilesPerCell;
|
||||
TerrainHeight = rows * offsets.SubTilesPerCell;
|
||||
|
||||
if (IsLoading)
|
||||
{
|
||||
_cachedTerrain = null;
|
||||
_cachedTerrainAreaHash = 0;
|
||||
Grid = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_wasLoading)
|
||||
{
|
||||
_cachedTerrain = null;
|
||||
_cachedTerrainAreaHash = 0;
|
||||
}
|
||||
|
||||
if (_cachedTerrain != null && _cachedTerrainAreaHash == AreaHash)
|
||||
{
|
||||
Grid = _cachedTerrain;
|
||||
WalkablePercent = CalcWalkablePercent(_cachedTerrain);
|
||||
return true;
|
||||
}
|
||||
|
||||
var gridBegin = t.WalkableGrid.First;
|
||||
var gridEnd = t.WalkableGrid.Last;
|
||||
if (gridBegin == 0 || gridEnd <= gridBegin)
|
||||
return true;
|
||||
|
||||
var gridDataSize = (int)(gridEnd - gridBegin);
|
||||
if (gridDataSize <= 0 || gridDataSize > 16 * 1024 * 1024)
|
||||
return true;
|
||||
|
||||
var bytesPerRow = t.BytesPerRow;
|
||||
if (bytesPerRow <= 0 || bytesPerRow > 0x10000)
|
||||
return true;
|
||||
|
||||
var gridWidth = cols * offsets.SubTilesPerCell;
|
||||
var gridHeight = rows * offsets.SubTilesPerCell;
|
||||
|
||||
var rawData = mem.ReadBytes(gridBegin, gridDataSize);
|
||||
if (rawData is null)
|
||||
return true;
|
||||
|
||||
var data = new byte[gridWidth * gridHeight];
|
||||
for (var row = 0; row < gridHeight; row++)
|
||||
{
|
||||
var rowStart = row * bytesPerRow;
|
||||
for (var col = 0; col < gridWidth; col++)
|
||||
{
|
||||
var byteIndex = rowStart + col / 2;
|
||||
if (byteIndex >= rawData.Length) break;
|
||||
|
||||
data[row * gridWidth + col] = (col % 2 == 0)
|
||||
? (byte)(rawData[byteIndex] & 0x0F)
|
||||
: (byte)((rawData[byteIndex] >> 4) & 0x0F);
|
||||
}
|
||||
}
|
||||
|
||||
var grid = new WalkabilityGrid(gridWidth, gridHeight, data);
|
||||
Grid = grid;
|
||||
WalkablePercent = CalcWalkablePercent(grid);
|
||||
|
||||
_cachedTerrain = grid;
|
||||
_cachedTerrainAreaHash = AreaHash;
|
||||
|
||||
Log.Information("Terrain grid read: {W}x{H} ({Cols}x{Rows} cells), {Pct}% walkable",
|
||||
gridWidth, gridHeight, cols, rows, WalkablePercent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Update loading edge state. Call after ReadData().</summary>
|
||||
public void UpdateLoadingEdge(bool isLoading)
|
||||
{
|
||||
_wasLoading = isLoading;
|
||||
}
|
||||
|
||||
protected override void Clear()
|
||||
{
|
||||
Grid = null;
|
||||
TerrainWidth = 0;
|
||||
TerrainHeight = 0;
|
||||
TerrainCols = 0;
|
||||
TerrainRows = 0;
|
||||
WalkablePercent = 0;
|
||||
}
|
||||
|
||||
public static int CalcWalkablePercent(WalkabilityGrid grid)
|
||||
{
|
||||
var walkable = 0;
|
||||
for (var i = 0; i < grid.Data.Length; i++)
|
||||
if (grid.Data[i] != 0) walkable++;
|
||||
return grid.Data.Length > 0 ? (int)(100L * walkable / grid.Data.Length) : 0;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue