mirror of
https://github.com/kwsch/NHSE.git
synced 2026-04-24 23:27:14 -05:00
Add logic to upgrade/downgrade item layers
Allows seamless imports of old maps on new saves, and vice versa.
This commit is contained in:
parent
f443add432
commit
f60a7e60e8
|
|
@ -1,23 +1,13 @@
|
|||
namespace NHSE.Core;
|
||||
|
||||
public sealed class FieldItemColumn
|
||||
{
|
||||
/// <summary> X Coordinate within the Field Item Layer </summary>
|
||||
public readonly int X;
|
||||
|
||||
/// <summary> Y Coordinate within the Field Item Layer </summary>
|
||||
public readonly int Y;
|
||||
|
||||
/// <summary> Offset relative to the start of the Field Item Layer </summary>
|
||||
public readonly int Offset;
|
||||
|
||||
public readonly byte[] Data;
|
||||
|
||||
public FieldItemColumn(int x, int y, int offset, byte[] data)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Offset = offset;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a list of item data tiles to be injected to a Field Item Layer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Extension tiles underneath the actual item root are included; extension tiles to the right are not.
|
||||
/// </remarks>
|
||||
/// <param name="X">X Coordinate within the Field Item Layer</param>
|
||||
/// <param name="Y">Y Coordinate within the Field Item Layer</param>
|
||||
/// <param name="Offset">Offset relative to the start of the Field Item Layer</param>
|
||||
/// <param name="Data">Data for this column</param>
|
||||
public sealed record FieldItemColumn(int X, int Y, int Offset, byte[] Data);
|
||||
125
NHSE.Core/Editing/FieldItem/FieldItemUpgrade.cs
Normal file
125
NHSE.Core/Editing/FieldItem/FieldItemUpgrade.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality to upgrade or downgrade field item data between different formats.
|
||||
/// </summary>
|
||||
public static class FieldItemUpgrade
|
||||
{
|
||||
private const int ColumnCountOld = 7;
|
||||
private const int ColumnCountNew = 9; // +1 column on each side
|
||||
private const int RowCount = 6;
|
||||
|
||||
private const byte SizeDim = FieldItemLayer.TilesPerAcreDim;
|
||||
private const int TilesPerAcre = SizeDim * SizeDim;
|
||||
private const int FieldItemSizeOld = (ColumnCountOld * RowCount) * TilesPerAcre * Item.SIZE;
|
||||
private const int FieldItemSizeNew = (ColumnCountNew * RowCount) * TilesPerAcre * Item.SIZE;
|
||||
private const int FieldItemSizeSingleColumn = RowCount * TilesPerAcre * Item.SIZE;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an update is needed based on the current size and expected size.
|
||||
/// </summary>
|
||||
/// <param name="current">Current size of the data.</param>
|
||||
/// <param name="expect">Desired size of the data.</param>
|
||||
/// <returns><see langword="true"/> if an update is needed; otherwise <see langword="false"/>.</returns>
|
||||
public static bool IsUpdateNeeded(long current, int expect) => expect switch
|
||||
{
|
||||
FieldItemSizeOld => current == FieldItemSizeNew,
|
||||
FieldItemSizeNew => current == FieldItemSizeOld,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Detects and performs an update on the field item data if needed.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to update.</param>
|
||||
/// <param name="expect">Desired size of the data.</param>
|
||||
/// <returns><see langword="true"/> if an update was performed; otherwise <see langword="false"/>.</returns>
|
||||
/// <remarks>Pre-check <see cref="IsUpdateNeeded(long, int)"/> to ensure a conversion is available.</remarks>
|
||||
public static bool DetectUpdate(ref byte[] data, int expect)
|
||||
{
|
||||
if (expect == FieldItemSizeNew && data.Length == FieldItemSizeOld)
|
||||
data = Inflate(data);
|
||||
else if (expect == FieldItemSizeOld && data.Length == FieldItemSizeNew)
|
||||
data = Deflate(data);
|
||||
else // No change needed/supported.
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Inflate(ReadOnlySpan{byte}, Span{byte})"/>
|
||||
public static byte[] Inflate(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != FieldItemSizeOld)
|
||||
throw new ArgumentException($"Data length {data.Length} does not match expected old size {FieldItemSizeOld}.");
|
||||
|
||||
var result = new byte[FieldItemSizeNew];
|
||||
Inflate(data, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflates old field item data to the new format.
|
||||
/// </summary>
|
||||
/// <param name="data">Old field item data.</param>
|
||||
/// <param name="result">Span to write new field item data to.</param>
|
||||
/// <returns>New field item data.</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static void Inflate(ReadOnlySpan<byte> data, Span<byte> result)
|
||||
{
|
||||
if (data.Length != FieldItemSizeOld)
|
||||
throw new ArgumentException($"Data length {data.Length} does not match expected old size {FieldItemSizeOld}.");
|
||||
if (result.Length < FieldItemSizeNew)
|
||||
throw new ArgumentException($"Result length {result.Length} is less than expected new size {FieldItemSizeNew}.");
|
||||
|
||||
// The first acre column is default field items.
|
||||
// Then, the existing data is present.
|
||||
// Finally, an acre column is default field items.
|
||||
|
||||
// Prepare a default column of no items.
|
||||
Span<byte> defaultColumn = stackalloc byte[FieldItemSizeSingleColumn];
|
||||
FillColumnWithItem(defaultColumn, Item.NONE);
|
||||
|
||||
// First default column
|
||||
defaultColumn.CopyTo(result);
|
||||
// Existing data
|
||||
data.CopyTo(result[FieldItemSizeSingleColumn..]);
|
||||
// Last default column
|
||||
defaultColumn.CopyTo(result[^FieldItemSizeSingleColumn..]);
|
||||
}
|
||||
|
||||
private static void FillColumnWithItem(Span<byte> defaultColumn, ulong tileValue)
|
||||
{
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
tileValue = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(tileValue);
|
||||
var cast = MemoryMarshal.Cast<byte, ulong>(defaultColumn);
|
||||
foreach (ref var value in cast)
|
||||
value = tileValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Deflate(ReadOnlySpan{byte}, Span{byte})"/>
|
||||
public static byte[] Deflate(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (data.Length != FieldItemSizeNew)
|
||||
throw new ArgumentException($"Data length {data.Length} does not match expected new size {FieldItemSizeNew}.");
|
||||
return data.Slice(FieldItemSizeSingleColumn, FieldItemSizeOld).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deflates new field item data to the old format.
|
||||
/// </summary>
|
||||
/// <param name="data">New field item data.</param>
|
||||
/// <param name="result">Span to write old field item data to.</param>
|
||||
/// <returns>Old field item data.</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static void Deflate(ReadOnlySpan<byte> data, Span<byte> result)
|
||||
{
|
||||
if (data.Length != FieldItemSizeNew)
|
||||
throw new ArgumentException($"Data length {data.Length} does not match expected new size {FieldItemSizeNew}.");
|
||||
if (result.Length < FieldItemSizeOld)
|
||||
throw new ArgumentException($"Result length {result.Length} is less than expected old size {FieldItemSizeOld}.");
|
||||
data.Slice(FieldItemSizeSingleColumn, FieldItemSizeOld).CopyTo(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -41,15 +41,15 @@ public static bool ImportToLayerAcreAll(FieldItemLayer layer)
|
|||
|
||||
var path = ofd.FileName;
|
||||
var fi = new FileInfo(path);
|
||||
|
||||
int expect = layer.TileInfo.TotalCount * Item.SIZE;
|
||||
if (fi.Length != expect)
|
||||
var expect = layer.TileInfo.TotalCount * Item.SIZE;
|
||||
if (fi.Length != expect && !FieldItemUpgrade.IsUpdateNeeded(fi.Length, expect))
|
||||
{
|
||||
WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expect));
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = File.ReadAllBytes(path);
|
||||
FieldItemUpgrade.DetectUpdate(ref data, expect);
|
||||
layer.ImportAll(data);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user