mirror of
https://github.com/kwsch/NHSE.git
synced 2026-08-28 13:54:24 -05:00
Misc tweaks
Still a WIP
This commit is contained in:
@@ -196,12 +196,19 @@ public void SetAcreBytes(ReadOnlySpan<byte> data)
|
||||
public TerrainTile[] GetTerrainTiles() => TerrainTile.GetArray(Data.Slice(Offsets.LandMakingMap, TotalTerrainTileCount * TerrainTile.SIZE));
|
||||
public void SetTerrainTiles(IReadOnlyList<TerrainTile> array) => TerrainTile.SetArray(array).CopyTo(Data[Offsets.LandMakingMap..]);
|
||||
|
||||
public const int MapDesignNone = 0xF800;
|
||||
public const ushort MapDesignNone = 0xF800;
|
||||
|
||||
public Memory<byte> MapDesignTileData => Raw.Slice(Offsets.MyDesignMap, 112 * 96 * sizeof(ushort));
|
||||
public ushort[] GetMapDesignTiles() => MemoryMarshal.Cast<byte, ushort>(MapDesignTileData.Span).ToArray();
|
||||
public void SetMapDesignTiles(ReadOnlySpan<ushort> value) => MemoryMarshal.Cast<ushort, byte>(value).CopyTo(MapDesignTileData.Span);
|
||||
|
||||
public void ClearDesignTiles()
|
||||
{
|
||||
var tiles = GetMapDesignTiles();
|
||||
tiles.AsSpan().Fill(MapDesignNone);
|
||||
SetMapDesignTiles(tiles);
|
||||
}
|
||||
|
||||
private int FieldItemLayerSize => TotalFieldItemTileCount * Item.SIZE;
|
||||
private int FieldItemFlagSize => TotalFieldItemTileCount / sizeof(byte); // bitflags
|
||||
|
||||
|
||||
@@ -46,12 +46,12 @@ protected EncryptedFilePair(ISaveFileProvider provider, string name)
|
||||
RawHeader = hd;
|
||||
RawData = md;
|
||||
|
||||
Info = RawHeader[..FileHeaderInfo.SIZE].ToClass<FileHeaderInfo>();
|
||||
Info = Header[..FileHeaderInfo.SIZE].ToArray().ToClass<FileHeaderInfo>();
|
||||
}
|
||||
|
||||
public void Save(uint seed)
|
||||
{
|
||||
var encrypt = Encryption.Encrypt(RawData, seed, RawHeader);
|
||||
var encrypt = Encryption.Encrypt(Data, seed, Header);
|
||||
Provider.WriteFile(NameData, encrypt.Data.Span);
|
||||
Provider.WriteFile(NameHeader, encrypt.Header.Span);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NHSE.Core;
|
||||
/// <summary>
|
||||
/// Simple design pattern
|
||||
/// </summary>
|
||||
public class DesignPattern : IVillagerOrigin
|
||||
public class DesignPattern(Memory<byte> Raw) : IVillagerOrigin
|
||||
{
|
||||
public const int Width = 32;
|
||||
public const int Height = 32;
|
||||
@@ -20,11 +20,8 @@ public class DesignPattern : IVillagerOrigin
|
||||
private const int PixelCount = 0x400; // Width * Height
|
||||
//private const int PixelDataSize = PixelCount / 2; // 4bit|4bit pixel packing
|
||||
|
||||
public readonly Memory<byte> Raw;
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public DesignPattern(Memory<byte> data) => Raw = data;
|
||||
|
||||
public uint Hash
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data);
|
||||
@@ -119,7 +116,13 @@ public static int GetColorOffset(int index)
|
||||
/// </summary>
|
||||
public byte[] GetBitmap()
|
||||
{
|
||||
byte[] data = new byte[4 * Width * Height];
|
||||
var result = new byte[4 * Width * Height];
|
||||
LoadBitmap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void LoadBitmap(Span<byte> data)
|
||||
{
|
||||
for (int i = 0; i < PixelCount; i++)
|
||||
{
|
||||
var choice = this[i];
|
||||
@@ -132,7 +135,6 @@ public byte[] GetBitmap()
|
||||
data[ofs + 0] = Data[palette + 2];
|
||||
data[ofs + 3] = 0xFF; // opaque
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,6 +143,12 @@ public byte[] GetBitmap()
|
||||
public byte[] GetPaletteBitmap()
|
||||
{
|
||||
var result = new byte[3 * PaletteColorCount];
|
||||
LoadPaletteBitmap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void LoadPaletteBitmap(Span<byte> result)
|
||||
{
|
||||
for (int i = 0; i < PaletteColorCount; i++)
|
||||
{
|
||||
var ofs = PaletteDataStart + (i * 3);
|
||||
@@ -148,6 +156,5 @@ public byte[] GetPaletteBitmap()
|
||||
result[(i * 3) + 1] = Data[ofs + 1];
|
||||
result[(i * 3) + 0] = Data[ofs + 2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace NHSE.Core;
|
||||
/// <summary>
|
||||
/// Advanced design pattern with 4 sheets arranged in a square.
|
||||
/// </summary>
|
||||
public class DesignPatternPRO : IVillagerOrigin
|
||||
public class DesignPatternPRO(Memory<byte> Raw) : IVillagerOrigin
|
||||
{
|
||||
public const int Width = 32;
|
||||
public const int Height = 32;
|
||||
@@ -21,11 +21,8 @@ public class DesignPatternPRO : IVillagerOrigin
|
||||
private const int PixelCount = 0x400; // Width * Height
|
||||
private const int SheetDataSize = PixelCount / 2; // 4bit|4bit pixel packing
|
||||
|
||||
public readonly Memory<byte> Raw;
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public DesignPatternPRO(Memory<byte> data) => Raw = data;
|
||||
|
||||
public uint Hash
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data);
|
||||
@@ -114,7 +111,13 @@ public static int GetColorOffset(int index)
|
||||
/// </summary>
|
||||
public byte[] GetBitmap(int sheet)
|
||||
{
|
||||
byte[] data = new byte[4 * Width * Height];
|
||||
var result = new byte[4 * Width * Height];
|
||||
LoadBitmap(sheet, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void LoadBitmap(int sheet, Span<byte> data)
|
||||
{
|
||||
for (int i = 0; i < PixelCount; i++)
|
||||
{
|
||||
var choice = GetPixelAtIndex(sheet, i);
|
||||
@@ -127,7 +130,6 @@ public byte[] GetBitmap(int sheet)
|
||||
data[ofs + 0] = Data[palette + 2];
|
||||
data[ofs + 3] = 0xFF; // opaque
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,6 +138,12 @@ public byte[] GetBitmap(int sheet)
|
||||
public byte[] GetPaletteBitmap()
|
||||
{
|
||||
var result = new byte[3 * PaletteColorCount];
|
||||
LoadPaletteBitmap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void LoadPaletteBitmap(Span<byte> result)
|
||||
{
|
||||
for (int i = 0; i < PaletteColorCount; i++)
|
||||
{
|
||||
var ofs = PaletteDataStart + (i * 3);
|
||||
@@ -143,6 +151,5 @@ public byte[] GetPaletteBitmap()
|
||||
result[(i * 3) + 1] = Data[ofs + 1];
|
||||
result[(i * 3) + 0] = Data[ofs + 2];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,7 @@ public void CopyFrom(Item item)
|
||||
|
||||
public static Item[] GetArray(ReadOnlySpan<byte> data) => data.GetArray<Item>(SIZE);
|
||||
public static byte[] SetArray(IReadOnlyList<Item> data) => data.SetArray(SIZE);
|
||||
public static byte[] SetArray(ReadOnlySpan<Item> data) => data.SetArray(SIZE);
|
||||
|
||||
public ushort GetWrappedItemName() => WrappingType switch
|
||||
{
|
||||
|
||||
161
NHSE.Core/Structures/Map/Layers/MapLayerConfigAcre.cs
Normal file
161
NHSE.Core/Structures/Map/Layers/MapLayerConfigAcre.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Configures how a layer rests within the Map's grid, relative to a "chunk" or "acre".
|
||||
/// </summary>
|
||||
/// <param name="CountWidth">Number of acres in the width direction.</param>
|
||||
/// <param name="CountHeight">Number of acres in the height direction.</param>
|
||||
/// <param name="ShiftWidth">Horizontal acre shift from the map's origin.</param>
|
||||
/// <param name="ShiftHeight">Vertical acre shift from the map's origin.</param>
|
||||
/// <param name="TilesPerAcre">Number of tiles per acre in one dimension (16 or 32).</param>
|
||||
/// <param name="TileBitShift">Bit shift value to convert between tiles and acres (4 for 16 tiles, 5 for 32 tiles).</param>
|
||||
public readonly record struct MapLayerConfigAcre(
|
||||
byte CountWidth, byte CountHeight,
|
||||
byte ShiftWidth, byte ShiftHeight,
|
||||
[ConstantExpected] byte TilesPerAcre, byte TileBitShift)
|
||||
{
|
||||
// Maps in Animal Crossing: New Horizons are made up of acres that are 9 tiles wide and 8 tiles high.
|
||||
// 5 columns in the center are land, surrounded by 2 tiles of beach and 2 tiles of sea on each side.
|
||||
// 4 rows in the center are land, surrounded by 2 rows of beach and 2 rows of sea on each side.
|
||||
// +-----------+
|
||||
// | ~~~~~~~~~ |
|
||||
// | ~*******~ |
|
||||
// | ~*=====*~ |
|
||||
// | ~*=====*~ |
|
||||
// | ~*=====*~ |
|
||||
// | ~*=====*~ |
|
||||
// | ~*******~ |
|
||||
// | ~~~~~~~~~ |
|
||||
// +-----------+
|
||||
|
||||
// Main Island Map Config - True Dimensions
|
||||
private const byte MapAcreWidth = 9; // 2 sea, 2 beach, 5 land
|
||||
private const byte MapAcreHeight = 8; // 2 sea, 2 beach, 4 land
|
||||
|
||||
// Optimize some calculations away by using bit-shift instead of mul/div, as we're always a multiple of 2.
|
||||
private const byte Grid32 = 32;
|
||||
private const byte Grid16 = 16;
|
||||
private const byte Shift32 = 5; // div32 is same as sh 5
|
||||
private const byte Shift16 = 4; // div16 is same as sh 4
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MapLayerConfigAcre"/> instance, centering the layer within the acre.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the layer in acres.</param>
|
||||
/// <param name="height">Height of the layer in acres.</param>
|
||||
/// <param name="tilesPerAcre">Number of tiles per acre (16 or 32).</param>
|
||||
/// <returns>A new <see cref="MapLayerConfigAcre"/> instance.</returns>
|
||||
public static MapLayerConfigAcre Create(byte width, byte height, [ConstantExpected(Min = Grid16, Max = Grid32)] byte tilesPerAcre)
|
||||
{
|
||||
var shiftW = (byte)((MapAcreWidth - width) / 2); // centered
|
||||
var shiftH = (byte)((MapAcreHeight - height) / 2); // centered
|
||||
|
||||
var bitShift = tilesPerAcre == Grid16 ? Shift16 : Shift32;
|
||||
#pragma warning disable CA1857
|
||||
return new MapLayerConfigAcre(width, height, shiftW, shiftH, tilesPerAcre, bitShift);
|
||||
#pragma warning restore CA1857
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts absolute coordinates to coordinates relative to the stored layer.
|
||||
/// </summary>
|
||||
/// <param name="absX">Absolute X coordinate on the map.</param>
|
||||
/// <param name="absY">Absolute Y coordinate on the map.</param>
|
||||
/// <param name="relX">Relative X coordinate in the layer.</param>
|
||||
/// <param name="relY">Relative Y coordinate in the layer.</param>
|
||||
/// <returns><see langword="true"/> if the absolute coordinates are within the layer; otherwise, <see langword="false"/>.</returns>
|
||||
public bool TryGetRelativeCoordinates(int absX, int absY, out int relX, out int relY)
|
||||
{
|
||||
relX = 0;
|
||||
relY = 0;
|
||||
|
||||
// Get relative acre
|
||||
var (acreX, acreY) = GetAbsoluteAcre(absX, absY);
|
||||
acreX -= ShiftWidth;
|
||||
acreY -= ShiftHeight;
|
||||
|
||||
// Performance: single if-check by using underflow casting to unsigned
|
||||
if ((uint)acreX >= CountWidth)
|
||||
return false;
|
||||
if ((uint)acreY >= CountHeight)
|
||||
return false;
|
||||
|
||||
// Return relative position
|
||||
relX = absX - (ShiftWidth << TileBitShift);
|
||||
relY = absY - (ShiftHeight << TileBitShift);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified absolute X and Y coordinates are within the valid bounds of the map.
|
||||
/// </summary>
|
||||
/// <param name="absX">The absolute X coordinate to validate. Must be within the horizontal bounds of the map.</param>
|
||||
/// <param name="absY">The absolute Y coordinate to validate. Must be within the vertical bounds of the map.</param>
|
||||
/// <returns><see langword="true"/> if the coordinates are valid; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsAbsoluteCoordinateValid(int absX, int absY)
|
||||
{
|
||||
if ((uint)absX >= ((uint)MapAcreWidth << TileBitShift))
|
||||
return false;
|
||||
if ((uint)absY >= ((uint)MapAcreHeight << TileBitShift))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute acre coordinates from absolute tile coordinates.
|
||||
/// </summary>
|
||||
public (int X, int Y) GetAbsoluteAcre(int absX, int absY)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absX, (uint)MapAcreWidth << TileBitShift);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absY, (uint)MapAcreHeight << TileBitShift);
|
||||
var acreX = absX >> TileBitShift;
|
||||
var acreY = absY >> TileBitShift;
|
||||
return (acreX, acreY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested tile index within the layer, given relative tile coordinates.
|
||||
/// </summary>
|
||||
/// <returns>The tile index within the layer.</returns>
|
||||
/// <inheritdoc cref="TryGetRelativeCoordinates(int, int, out int, out int)"/>
|
||||
public int GetIndexTileRelative(int relX, int relY)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)relX, (uint)CountWidth << TileBitShift);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)relY, (uint)CountHeight << TileBitShift);
|
||||
// Tile ordering is top-down, left-to-right.
|
||||
// In other words, Item[1] is X=0,Y=1
|
||||
return (relX * (CountHeight << TileBitShift)) + relY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested tile index within the absolute map boundary, given absolute tile coordinates in the map.
|
||||
/// </summary>
|
||||
/// <returns>The tile index within the map.</returns>
|
||||
/// <inheritdoc cref="TryGetRelativeCoordinates(int, int, out int, out int)"/>
|
||||
public int GetIndexTileAbsolute(int absX, int absY)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absX, (uint)MapAcreWidth << TileBitShift);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absY, (uint)MapAcreHeight << TileBitShift);
|
||||
// Tile ordering is top-down, left-to-right.
|
||||
// In other words, Item[1] is X=0,Y=1
|
||||
return (absX * (MapAcreHeight << TileBitShift)) + absY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the acre index (not the value selection of the acre) based on the absolute coordinates on the map.
|
||||
/// </summary>
|
||||
/// <returns>The acre index within the map.</returns>
|
||||
/// <inheritdoc cref="TryGetRelativeCoordinates(int, int, out int, out int)"/>
|
||||
public int GetIndexAcre(int absX, int absY)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absX, MapAcreWidth);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)absY, MapAcreHeight);
|
||||
|
||||
// Acre ordering is top-down, left-to-right.
|
||||
var (x, y) = GetAbsoluteAcre(absX, absY);
|
||||
return (x * MapAcreHeight) + y;
|
||||
}
|
||||
}
|
||||
@@ -36,14 +36,15 @@ public TerrainLayer(TerrainTile[] tiles, Memory<byte> acres) : base(Viewport)
|
||||
set => Tiles[index] = value;
|
||||
}
|
||||
|
||||
public byte[] DumpAll()
|
||||
{
|
||||
var result = new byte[Tiles.Length * TerrainTile.SIZE];
|
||||
for (int i = 0; i < Tiles.Length; i++)
|
||||
Tiles[i].ToBytesClass().CopyTo(result, i * TerrainTile.SIZE);
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// Flattens the terrain tiles into a contiguous byte array.
|
||||
/// </summary>
|
||||
public byte[] DumpAll() => TerrainTile.SetArray(Tiles);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tiles local to the specified acre as a contiguous byte array.
|
||||
/// </summary>
|
||||
/// <param name="acre">Terrain acre index.</param>
|
||||
public byte[] DumpAcre(int acre)
|
||||
{
|
||||
int count = TileInfo.ViewCount;
|
||||
@@ -58,6 +59,10 @@ public byte[] DumpAcre(int acre)
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports terrain tiles from a contiguous byte array.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array containing terrain tile data.</param>
|
||||
public void ImportAll(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var tiles = TerrainTile.GetArray(data);
|
||||
@@ -65,6 +70,11 @@ public void ImportAll(ReadOnlySpan<byte> data)
|
||||
Tiles[i].CopyFrom(tiles[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports terrain tiles for the specified acre from a contiguous byte array.
|
||||
/// </summary>
|
||||
/// <param name="acre">Terrain acre index.</param>
|
||||
/// <param name="data">Byte array containing terrain tile data.</param>
|
||||
public void ImportAcre(int acre, ReadOnlySpan<byte> data)
|
||||
{
|
||||
int count = TileInfo.ViewCount;
|
||||
@@ -76,7 +86,12 @@ public void ImportAcre(int acre, ReadOnlySpan<byte> data)
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAll(TerrainTile tile, in bool interiorOnly)
|
||||
/// <summary>
|
||||
/// Sets all tiles to the specified tile.
|
||||
/// </summary>
|
||||
/// <param name="tile"></param>
|
||||
/// <param name="interiorOnly">If true, only sets the interior tiles, skipping the outermost ring of beach/rock acres.</param>
|
||||
public void SetAll(TerrainTile tile, in bool interiorOnly = true)
|
||||
{
|
||||
if (interiorOnly)
|
||||
{
|
||||
@@ -98,7 +113,12 @@ public void SetAll(TerrainTile tile, in bool interiorOnly)
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAllRoad(TerrainTile tile, in bool interiorOnly)
|
||||
/// <summary>
|
||||
/// Sets all road tiles (not the terrain itself, but the road atop) to the specified tile.
|
||||
/// </summary>
|
||||
/// <param name="tile">Road tile info to copy from.</param>
|
||||
/// <param name="interiorOnly">If true, only sets the interior tiles, skipping the outermost ring of beach/rock acres.</param>
|
||||
public void SetAllRoad(TerrainTile tile, bool interiorOnly = true)
|
||||
{
|
||||
if (interiorOnly)
|
||||
{
|
||||
@@ -163,6 +183,7 @@ public int GetTileColor(int x, in int y, int relativeX, int relativeY)
|
||||
|
||||
private ushort GetTileAcre(int x, int y)
|
||||
{
|
||||
// Acres are 16x16 tiles, and the acre data has a 1-acre deep-sea border around it.
|
||||
var acreX = 1 + (x / 16);
|
||||
var acreY = 1 + (y / 16);
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ public void Save()
|
||||
/// <summary>
|
||||
/// Lists out all coordinates of tiles present in <see cref="Layer2"/> that don't have anything underneath in <see cref="Layer1"/> to support them.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<string> GetUnsupportedTiles(int totalWidth, int totalHeight)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
@@ -78,5 +78,4 @@ private void SetTopLeftNearest(ref int x, ref int y)
|
||||
int maxY = TotalHeight - ViewHeight;
|
||||
ClampCoordinatesTo(ref x, ref y, maxX, maxY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,49 +13,14 @@ public struct GSavePlayerManpu : IReactionStore
|
||||
private const int MaxCount = 64;
|
||||
private const int WheelCount = 8;
|
||||
|
||||
/// <summary>
|
||||
/// List of known Reaction IDs
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxCount)]
|
||||
public Reaction[] ManpuBit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Emotions that are currently bound to the Reaction Wheel.
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, SizeConst = WheelCount)]
|
||||
public Reaction[] UIList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating if a Reaction (at the same index?) is newly learned or not.
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = MaxCount)]
|
||||
public bool[] NewFlag { get; set; }
|
||||
|
||||
public void AddMissingReactions()
|
||||
{
|
||||
var all = Enum.GetValues<Reaction>();
|
||||
foreach (var react in all)
|
||||
AddReaction(react);
|
||||
}
|
||||
|
||||
// returns true if failed
|
||||
public bool AddReaction(Reaction react)
|
||||
{
|
||||
if (react.ToString().StartsWith("UNUSED"))
|
||||
return true;
|
||||
|
||||
var index = Array.IndexOf(ManpuBit, react);
|
||||
if (index >= 0)
|
||||
return false;
|
||||
|
||||
var empty = EmptyIndex;
|
||||
if (empty < 0)
|
||||
return true;
|
||||
ManpuBit[empty] = react;
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly int EmptyIndex => Array.FindIndex(ManpuBit, z => z == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,56 +33,64 @@ public struct GSavePlayerManpu15 : IReactionStore
|
||||
private const int MaxCount = 256; // up from 64
|
||||
private const int WheelCount = 8;
|
||||
|
||||
/// <summary>
|
||||
/// List of known Reaction IDs
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxCount)]
|
||||
public Reaction[] ManpuBit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Emotions that are currently bound to the Reaction Wheel.
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, SizeConst = WheelCount)]
|
||||
public Reaction[] UIList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating if a Reaction (at the same index?) is newly learned or not.
|
||||
/// </summary>
|
||||
[field: MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = MaxCount)]
|
||||
public bool[] NewFlag { get; set; }
|
||||
|
||||
public void AddMissingReactions()
|
||||
{
|
||||
var all = Enum.GetValues<Reaction>();
|
||||
foreach (var react in all)
|
||||
AddReaction(react);
|
||||
}
|
||||
|
||||
// returns true if failed
|
||||
public bool AddReaction(Reaction react)
|
||||
{
|
||||
if (react.ToString().StartsWith("UNUSED"))
|
||||
return true;
|
||||
|
||||
var index = Array.IndexOf(ManpuBit, react);
|
||||
if (index >= 0)
|
||||
return false;
|
||||
|
||||
var empty = EmptyIndex;
|
||||
if (empty < 0)
|
||||
return true;
|
||||
ManpuBit[empty] = react;
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly int EmptyIndex => Array.FindIndex(ManpuBit, z => z == 0);
|
||||
}
|
||||
|
||||
public interface IReactionStore
|
||||
{
|
||||
/// <summary>
|
||||
/// List of Reaction IDs the player currently knows.
|
||||
/// </summary>
|
||||
Reaction[] ManpuBit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Emotions that are currently bound to the Reaction Wheel.
|
||||
/// </summary>
|
||||
Reaction[] UIList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating if a Reaction (at the same index?) is newly learned or not.
|
||||
/// </summary>
|
||||
bool[] NewFlag { get; set; }
|
||||
bool AddReaction(Reaction react);
|
||||
void AddMissingReactions();
|
||||
|
||||
/// <summary>
|
||||
/// Adds all possible reaction values from <see cref="Reaction"/>'s defined list.
|
||||
/// </summary>
|
||||
void AddMissingReactions()
|
||||
{
|
||||
var all = Enum.GetValues<Reaction>();
|
||||
foreach (var react in all)
|
||||
TryAddReaction(react);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to add the <see cref="react"/> to the list of reactions.
|
||||
/// </summary>
|
||||
/// <param name="react">Reaction to add to list</param>
|
||||
bool TryAddReaction(Reaction react)
|
||||
{
|
||||
if (react.ToString().StartsWith("UNUSED"))
|
||||
return false; // shouldn't add
|
||||
|
||||
if (ManpuBit.Contains(react))
|
||||
return true; // already have
|
||||
|
||||
var empty = EmptyIndex;
|
||||
if (empty < 0)
|
||||
return true; // full? already have
|
||||
ManpuBit[empty] = react;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First empty index within the array of reactions.
|
||||
/// </summary>
|
||||
int EmptyIndex => ManpuBit.IndexOf(Reaction.None);
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
@@ -11,29 +9,6 @@ public class MapManager(MainSave sav) : MapTerrainStructure(sav)
|
||||
public int MapLayer { get; set; } // 0 or 1
|
||||
|
||||
public FieldItemLayer CurrentLayer => MapLayer == 0 ? Items.Layer1 : Items.Layer2;
|
||||
|
||||
public static void ClearDesignTiles(MainSave sav)
|
||||
{
|
||||
var tiles = sav.GetMapDesignTiles();
|
||||
for (int i = 0; i < tiles.Length; i++)
|
||||
tiles[i] = MainSave.MapDesignNone;
|
||||
sav.SetMapDesignTiles(tiles);
|
||||
}
|
||||
|
||||
public static byte[] ExportDesignTiles(MainSave sav)
|
||||
{
|
||||
var tiles = sav.GetMapDesignTiles();
|
||||
var result = new byte[tiles.Length * 2];
|
||||
Buffer.BlockCopy(tiles, 0, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void ImportDesignTiles(MainSave sav, ReadOnlySpan<byte> result)
|
||||
{
|
||||
var tiles = sav.GetMapDesignTiles();
|
||||
MemoryMarshal.Cast<byte, ushort>(result).CopyTo(tiles);
|
||||
sav.SetMapDesignTiles(tiles);
|
||||
}
|
||||
}
|
||||
|
||||
public class MapTerrainStructure(MainSave sav)
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
public class PlayerRoom1 : IPlayerRoom
|
||||
public class PlayerRoom1(Memory<byte> raw) : IPlayerRoom
|
||||
{
|
||||
public const int SIZE = 0x65C8;
|
||||
public virtual string Extension => "nhpr";
|
||||
|
||||
public readonly Memory<byte> Raw;
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public PlayerRoom1(Memory<byte> data) => Raw = data;
|
||||
public Span<byte> Data => raw.Span;
|
||||
|
||||
public byte[] Write() => Data.ToArray();
|
||||
|
||||
@@ -24,7 +21,7 @@ public class PlayerRoom1 : IPlayerRoom
|
||||
|
||||
public const int LayerCount = 8;
|
||||
public RoomItemLayer[] GetItemLayers() => RoomItemLayer.GetArray(Data[..(LayerCount * RoomItemLayer.SIZE)].ToArray());
|
||||
public void SetItemLayers(IReadOnlyList<RoomItemLayer> value) => RoomItemLayer.SetArray(value).AsSpan().CopyTo(Data);
|
||||
public void SetItemLayers(IReadOnlyList<RoomItemLayer> value) => RoomItemLayer.SetArray(value).CopyTo(Data);
|
||||
|
||||
public bool GetIsActive(int layer, int x, int y) => FlagUtil.GetFlag(Data, 0x6400 + (layer * 0x34), (y * 20) + x);
|
||||
public void SetIsActive(int layer, int x, int y, bool value = true) => FlagUtil.SetFlag(Data, 0x6400 + (layer * 0x34), (y * 20) + x, value);
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
public class PlayerRoom2 : PlayerRoom1
|
||||
public class PlayerRoom2(Memory<byte> raw) : PlayerRoom1(raw)
|
||||
{
|
||||
public new const int SIZE = 0x6C24;
|
||||
public new virtual string Extension => "nhpr2";
|
||||
|
||||
public PlayerRoom2(Memory<byte> data) : base(data) { }
|
||||
|
||||
/*
|
||||
s_665e9093 ExtraEffectLayerList[2]; // @0x65c8 size 0x320, align 2
|
||||
GSaveMusicBoxInfo MusicBoxInfo; // @0x6c08 size 0x4, align 2
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NHSE.Core;
|
||||
@@ -82,6 +83,14 @@ public static class StructConverter
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] SetArray<T>(this ReadOnlySpan<T> data, int size) where T : class
|
||||
{
|
||||
var result = new byte[data.Length * size];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i].ToBytesClass().CopyTo(result, i * size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] SetArrayStructure<T>(this ReadOnlySpan<T> data, int size) where T : struct
|
||||
{
|
||||
var result = new byte[data.Length * size];
|
||||
|
||||
@@ -74,7 +74,7 @@ public static string[] GetArtList(string msgPath)
|
||||
result.Add($"{itemID:00000}, // {Text}{fake}");
|
||||
}
|
||||
result.Sort();
|
||||
return result.ToArray();
|
||||
return [..result];
|
||||
}
|
||||
|
||||
public static string[] GetVillagerPhraseResource(string msgPath)
|
||||
@@ -82,7 +82,8 @@ public static string[] GetVillagerPhraseResource(string msgPath)
|
||||
var file = Path.Combine(msgPath, "Npc", "STR_NNpcPhrase.msbt");
|
||||
var list = GetLabelList(file);
|
||||
var normal = list.Select(z => $"{z.Label}\t{z.Text}").Order();
|
||||
return normal.ToArray();
|
||||
|
||||
return [..normal];
|
||||
}
|
||||
|
||||
public static string[] GetVillagerListResource(string msgPath)
|
||||
@@ -95,7 +96,7 @@ public static string[] GetVillagerListResource(string msgPath)
|
||||
list = GetLabelList(file);
|
||||
var special = list.Select(z => $"{z.Label}\t{z.Text}").Order();
|
||||
|
||||
return normal.Concat(special).ToArray();
|
||||
return [..normal, ..special];
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, string> GetItemList(string msgPath, string language)
|
||||
|
||||
@@ -4,8 +4,15 @@
|
||||
|
||||
namespace NHSE.Sprites;
|
||||
|
||||
/// <summary>
|
||||
/// Produces bitmaps for viewing map acres and full maps.
|
||||
/// </summary>
|
||||
public sealed class MapViewer : MapView, IDisposable
|
||||
{
|
||||
private const byte ScaleAsMap = 2;
|
||||
private const byte FieldItemWidthOld = 7;
|
||||
private const byte FieldItemWidthNew = 9;
|
||||
|
||||
// Cached acre view objects to remove allocation/GC
|
||||
private readonly int[] PixelsItemAcre1;
|
||||
private readonly int[] PixelsItemAcreX;
|
||||
@@ -53,18 +60,35 @@ public void Dispose()
|
||||
|
||||
public Bitmap GetBackgroundTerrain(int index = -1)
|
||||
{
|
||||
return TerrainSprite.GetMapWithBuildings(Map, null, PixelsBackgroundMap1, PixelsBackgroundMapX, BackgroundMap, 2, index);
|
||||
return TerrainSprite.GetMapWithBuildings(Map, null, PixelsBackgroundMap1, PixelsBackgroundMapX, BackgroundMap, ScaleAsMap, index);
|
||||
}
|
||||
|
||||
private Bitmap GetLayerAcre(int topX, int topY, int t)
|
||||
public Bitmap GetInflatedImage(Bitmap regular)
|
||||
{
|
||||
// Insert 1 acre on each side
|
||||
int columnWidth = (regular.Width / FieldItemWidthOld);
|
||||
var newWidth = columnWidth * FieldItemWidthNew;
|
||||
var bmp = new Bitmap(newWidth, regular.Height);
|
||||
using var g = Graphics.FromImage(bmp);
|
||||
|
||||
// Fill with blue
|
||||
// g.Clear(Color.FromArgb(100, 149, 237));
|
||||
|
||||
// Draw regular centered to new bitmap
|
||||
g.DrawImage(regular, columnWidth, 0, regular.Width, regular.Height);
|
||||
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private Bitmap GetLayerAcre(int topX, int topY, int transparency)
|
||||
{
|
||||
var layer = Map.CurrentLayer;
|
||||
return ItemLayerSprite.GetBitmapItemLayerViewGrid(layer, topX, topY, AcreScale, PixelsItemAcre1, PixelsItemAcreX, ScaleAcre, t);
|
||||
return ItemLayerSprite.GetBitmapItemLayerViewGrid(layer, topX, topY, AcreScale, PixelsItemAcre1, PixelsItemAcreX, ScaleAcre, transparency);
|
||||
}
|
||||
|
||||
public Bitmap GetBackgroundAcre(Font f, byte tbuild, byte tterrain, int index = -1)
|
||||
public Bitmap GetBackgroundAcre(Font f, byte transparencyBuilding, byte transparencyTerrain, int index = -1)
|
||||
{
|
||||
return TerrainSprite.GetAcre(this, f, PixelsBackgroundAcre1, PixelsBackgroundAcreX, BackgroundAcre, index, tbuild, tterrain);
|
||||
return TerrainSprite.GetAcre(this, f, PixelsBackgroundAcre1, PixelsBackgroundAcreX, BackgroundAcre, index, transparencyBuilding, transparencyTerrain);
|
||||
}
|
||||
|
||||
private Bitmap GetMapWithReticle(int topX, int topY, int t, FieldItemLayer layer)
|
||||
|
||||
@@ -30,7 +30,7 @@ public Main()
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
|
||||
var args = Environment.GetCommandLineArgs().AsSpan();
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (Directory.Exists(arg))
|
||||
|
||||
@@ -17,6 +17,7 @@ public sealed partial class FieldItemEditor : Form, IItemLayerEditor
|
||||
|
||||
private readonly MapManager Map;
|
||||
private readonly MapViewer View;
|
||||
private readonly bool IsExtendedMap30;
|
||||
|
||||
private bool Loading;
|
||||
private int SelectedBuildingIndex;
|
||||
@@ -37,8 +38,9 @@ public FieldItemEditor(MainSave sav)
|
||||
InitializeComponent();
|
||||
this.TranslateInterface(GameInfo.CurrentLanguage);
|
||||
|
||||
var scale = (PB_Acre.Width - 2) / 32;
|
||||
var scale = (PB_Acre.Width - 2) / FieldItemLayer.TilesPerAcreDim; // 1px border
|
||||
SAV = sav;
|
||||
IsExtendedMap30 = sav.FieldItemAcreWidth != 7;
|
||||
Map = new MapManager(sav);
|
||||
View = new MapViewer(Map, scale);
|
||||
|
||||
@@ -57,8 +59,11 @@ public FieldItemEditor(MainSave sav)
|
||||
|
||||
private void LoadComboBoxes()
|
||||
{
|
||||
// Snap viewport to acre
|
||||
foreach (var acre in AcreCoordinate.Acres)
|
||||
CB_Acre.Items.Add(acre.Name);
|
||||
|
||||
// Select acre type for current
|
||||
foreach (var acre in AcreCoordinate.Exterior)
|
||||
CB_MapAcre.Items.Add(acre.Name);
|
||||
|
||||
@@ -113,20 +118,36 @@ private void LoadItemGridAcre()
|
||||
|
||||
private void ReloadMapBackground()
|
||||
{
|
||||
PB_Map.BackgroundImage = View.GetBackgroundTerrain(SelectedBuildingIndex);
|
||||
var img = View.GetBackgroundTerrain(SelectedBuildingIndex);
|
||||
SetMapBackgroundImage(img);
|
||||
}
|
||||
|
||||
private void ReloadMapItemGrid() => SetMapForegroundImage(View.GetMapWithReticle(GetItemTransparency()));
|
||||
|
||||
private void SetMapBackgroundImage(Bitmap img)
|
||||
{
|
||||
if (IsExtendedMap30)
|
||||
img = View.GetInflatedImage(img);
|
||||
PB_Map.BackgroundImage = img;
|
||||
PB_Map.Invalidate(); // background image reassigning to same img doesn't redraw; force it
|
||||
}
|
||||
|
||||
private void SetMapForegroundImage(Bitmap img)
|
||||
{
|
||||
if (IsExtendedMap30)
|
||||
img = View.GetInflatedImage(img);
|
||||
PB_Map.Image = img;
|
||||
}
|
||||
|
||||
private void ReloadAcreBackground()
|
||||
{
|
||||
var tbuild = (byte)TR_BuildingTransparency.Value;
|
||||
var tterrain = (byte)TR_Terrain.Value;
|
||||
PB_Acre.BackgroundImage = View.GetBackgroundAcre(L_Coordinates.Font, tbuild, tterrain, SelectedBuildingIndex);
|
||||
var img = View.GetBackgroundAcre(L_Coordinates.Font, tbuild, tterrain, SelectedBuildingIndex);
|
||||
PB_Acre.BackgroundImage = img;
|
||||
PB_Acre.Invalidate(); // background image reassigning to same img doesn't redraw; force it
|
||||
}
|
||||
|
||||
private void ReloadMapItemGrid() => PB_Map.Image = View.GetMapWithReticle(GetItemTransparency());
|
||||
|
||||
private void ReloadAcreItemGrid() => PB_Acre.Image = View.GetLayerAcre(GetItemTransparency());
|
||||
|
||||
public void ReloadItems()
|
||||
@@ -1069,7 +1090,7 @@ private void B_SetAllRoadTiles_Click(object sender, EventArgs e)
|
||||
|
||||
private void B_ClearPlacedDesigns_Click(object sender, EventArgs e)
|
||||
{
|
||||
MapManager.ClearDesignTiles(SAV);
|
||||
SAV.ClearDesignTiles();
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
}
|
||||
|
||||
@@ -1082,7 +1103,7 @@ private void B_ExportPlacedDesigns_Click(object sender, EventArgs e)
|
||||
return;
|
||||
|
||||
string path = sfd.FileName;
|
||||
var tiles = MapManager.ExportDesignTiles(SAV);
|
||||
var tiles = SAV.MapDesignTileData.Span;
|
||||
File.WriteAllBytes(path, tiles);
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
}
|
||||
@@ -1097,7 +1118,7 @@ private void B_ImportPlacedDesigns_Click(object sender, EventArgs e)
|
||||
|
||||
string path = ofd.FileName;
|
||||
var tiles = File.ReadAllBytes(path);
|
||||
MapManager.ImportDesignTiles(SAV, tiles);
|
||||
tiles.CopyTo(SAV.MapDesignTileData.Span);
|
||||
System.Media.SystemSounds.Asterisk.Play();
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public static bool LoadMuseum(Museum museum)
|
||||
}
|
||||
|
||||
var data = File.ReadAllBytes(file);
|
||||
data.AsSpan().CopyTo(museum.Data);
|
||||
data.CopyTo(museum.Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user