mirror of
https://github.com/kwsch/NHSE.git
synced 2026-03-22 01:34:51 -05:00
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.
167 lines
5.1 KiB
C#
167 lines
5.1 KiB
C#
using System;
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
|
|
|
namespace NHSE.Core;
|
|
|
|
/// <summary>
|
|
/// Simple design pattern
|
|
/// </summary>
|
|
public sealed class DesignPattern(Memory<byte> Raw) : IVillagerOrigin
|
|
{
|
|
public const int Width = 32;
|
|
public const int Height = 32;
|
|
|
|
public const int SIZE = 0x2A8; // 3 bytes unused at end
|
|
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 PixelDataSize = 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);
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the color choice (1-15) for the pixel at the given <see cref="index"/>.
|
|
/// </summary>
|
|
/// <param name="index">Pixel index</param>
|
|
public int this[int index]
|
|
{
|
|
get
|
|
{
|
|
var ofs = PixelDataOffset + (index / 2);
|
|
var val = Data[ofs];
|
|
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
|
|
}
|
|
set
|
|
{
|
|
var ofs = PixelDataOffset + (index / 2);
|
|
var val = Data[ofs];
|
|
var update = ((index & 1) == 0)
|
|
? (val & 0xF0) | (value & 0xF)
|
|
: ((value & 0xF) << 4) | (val & 0xF);
|
|
Data[ofs] = (byte)update;
|
|
}
|
|
}
|
|
|
|
public static int GetPixelIndex(int x, int y) => (y * Height) + x;
|
|
|
|
public int GetPixel(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(x, y);
|
|
return this[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()
|
|
{
|
|
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];
|
|
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;
|
|
}
|
|
|
|
public 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];
|
|
}
|
|
}
|
|
} |