NHSE/NHSE.Core/Structures/Designs/DesignPatternPRO.cs
Josh (vector_cmdr) 41841e5bfb
Feat: Post Box Editing (#744)
Feature:
Adds support for postbox.dat via Post Box Editor and accompanying components:
- Add Mail class.
- Add PostBox class and corresponding offset classes.
- Add wrap suppression option to ItemEditor.
- Add wrapping and box dictionary to ItemWrapping class.
- Add new language strings associated with forms and types.

Fix:
Resolved overwrite for bad dumps on all imports by adding UsageCompatibilityOffset and UsageCompatibility() added to Design classes. Transparent pixel check method added to PatternEditor and LoadPattern() altered to write the correct compatibility bytes based on type and transparency.
2026-02-10 01:32:16 +11:00

162 lines
5.2 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core;
/// <summary>
/// Advanced design pattern with 4 sheets arranged in a square.
/// </summary>
public sealed class DesignPatternPRO(Memory<byte> Raw) : 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 UsageCompatibilityOffset = 0x70;
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 Span<byte> Data => Raw.Span;
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 uint UsageCompatibility
{
get => ReadUInt32LittleEndian(Data[UsageCompatibilityOffset..]);
set => WriteUInt32LittleEndian(Data[UsageCompatibilityOffset..], value);
}
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)
{
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);
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
}
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
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);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
}
}