using System; using System.Collections.Generic; using System.IO; using System.Numerics; using Dalamud.Data; using Penumbra.GameData.Structs; namespace Penumbra.GameData.Files; public partial class StmFile { public const string Path = "chara/base_material/stainingtemplate.stm"; /// /// All dye-able color set information for a row. /// public record struct DyePack { public Vector3 Diffuse; public Vector3 Specular; public Vector3 Emissive; public float Gloss; public float SpecularPower; } /// /// All currently available dyeing templates with their IDs. /// public readonly IReadOnlyDictionary Entries; /// /// Access a specific dye pack. /// /// The ID of the accessed template. /// The ID of the Stain. /// The corresponding color set information or a defaulted DyePack of 0-entries. public DyePack this[ushort template, int idx] => Entries.TryGetValue(template, out var entry) ? entry[idx] : default; /// public DyePack this[ushort template, StainId idx] => this[template, (int)idx.Value]; /// /// Try to access a specific dye pack. /// /// The ID of the accessed template. /// The ID of the Stain. /// On success, the corresponding color set information, otherwise a defaulted DyePack. /// True on success, false otherwise. public bool TryGetValue(ushort template, StainId idx, out DyePack dyes) { if (idx.Value is > 0 and <= StainingTemplateEntry.NumElements && Entries.TryGetValue(template, out var entry)) { dyes = entry[idx]; return true; } dyes = default; return false; } /// /// Create a STM file from the given data array. /// public StmFile(byte[] data) { using var stream = new MemoryStream(data); using var br = new BinaryReader(stream); br.ReadUInt32(); var numEntries = br.ReadInt32(); var keys = new ushort[numEntries]; var offsets = new ushort[numEntries]; for (var i = 0; i < numEntries; ++i) keys[i] = br.ReadUInt16(); for (var i = 0; i < numEntries; ++i) offsets[i] = br.ReadUInt16(); var entries = new Dictionary(numEntries); Entries = entries; for (var i = 0; i < numEntries; ++i) { var offset = offsets[i] * 2 + 8 + 4 * numEntries; entries.Add(keys[i], new StainingTemplateEntry(br, offset)); } } /// /// Try to read and parse the default STM file given by Lumina. /// public StmFile(DataManager gameData) : this(gameData.GetFile(Path)?.Data ?? Array.Empty()) { } }