NHSE/NHSE.Core/Structures/Designs/DesignPatternPRO.cs
2026-01-13 16:02:58 -06:00

149 lines
4.9 KiB
C#

using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core;
/// <summary>
/// Advanced design pattern with 4 sheets arranged in a square.
/// </summary>
public class DesignPatternPRO : IVillagerOrigin
{
public const int Width = 32;
public const int Height = 32;
public const int SIZE = 0x8A8; // 3 bytes unused at end
public const int SheetCount = 4;
private const int PersonalOffset = 0x38;
private const int PaletteDataStart = 0x78;
public const int PaletteColorCount = 15; // y not 16???
private const int PaletteColorSize = 3; // R, G, B
private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5
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);
set => WriteUInt32LittleEndian(Data, value);
}
public uint Version
{
get => ReadUInt32LittleEndian(Data[0x04..]);
set => WriteUInt32LittleEndian(Data[0x04..], value);
}
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data[0x10..]);
}
public uint TownID
{
get => ReadUInt32LittleEndian(Data[PersonalOffset..]);
set => WriteUInt32LittleEndian(Data[PersonalOffset..], value);
}
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x04)..]);
}
public Span<byte> GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public uint PlayerID
{
get => ReadUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..]);
set => WriteUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..], value);
}
public string PlayerName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x20)..]);
}
public Span<byte> GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public void SetPixelAtIndex(int sheet, int index, int value)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: (value & 0xF) << 4 | (val & 0xF);
Data[ofs] = (byte) update;
}
private int GetPixelAtIndex(int sheet, int index)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
}
public static int GetPixelIndex(int sheet, int x, int y) => (sheet * SheetDataSize) + (y * Height) + x;
public int GetPixel(int sheet, int x, int y)
{
if ((uint)x >= Width)
throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x));
if ((uint)y >= Height)
throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y));
var index = GetPixelIndex(sheet, x, y);
return GetPixelAtIndex(sheet, index);
}
public static int GetColorOffset(int index)
{
if ((uint)index >= PaletteColorCount)
throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index));
return PaletteDataStart + (index * PaletteColorSize);
}
/// <summary>
/// Builds a new array with unpacked 32bit pixel data.
/// </summary>
public byte[] GetBitmap(int sheet)
{
byte[] data = new byte[4 * Width * Height];
for (int i = 0; i < PixelCount; i++)
{
var choice = GetPixelAtIndex(sheet, i);
if (choice == PaletteColorCount)
continue; // transparent?
var palette = GetColorOffset(choice);
var ofs = i * 4;
data[ofs + 2] = Data[palette + 0];
data[ofs + 1] = Data[palette + 1];
data[ofs + 0] = Data[palette + 2];
data[ofs + 3] = 0xFF; // opaque
}
return data;
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
public byte[] GetPaletteBitmap()
{
var result = new byte[3 * PaletteColorCount];
for (int i = 0; i < PaletteColorCount; i++)
{
var ofs = PaletteDataStart + (i * 3);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
return result;
}
}