Use strongly typed ids in most places.

This commit is contained in:
Ottermandias 2023-07-29 02:22:31 +02:00
parent dccd347432
commit 18b6b87e6b
26 changed files with 192 additions and 193 deletions

@ -1 +1 @@
Subproject commit 98bd4e9946ded20cb5d54182883e73f344fe2d26 Subproject commit 263bfb49c998700197a18ad99fa1daadc8736f5d

View file

@ -5,6 +5,7 @@ using System.Linq;
using OtterGui; using OtterGui;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta; using Penumbra.Meta;
@ -48,7 +49,7 @@ public readonly struct EqdpCache : IDisposable
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>()) foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
{ {
var relevant = CharacterUtility.RelevantIndices[file.Index.Value]; var relevant = CharacterUtility.RelevantIndices[file.Index.Value];
file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (int)m.SetId)); file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (SetId) m.SetId));
} }
_eqdpManipulations.Clear(); _eqdpManipulations.Clear();

View file

@ -32,7 +32,7 @@ public struct EqpCache : IDisposable
if (_eqpFile == null) if (_eqpFile == null)
return; return;
_eqpFile.Reset(_eqpManipulations.Select(m => (int)m.SetId)); _eqpFile.Reset(_eqpManipulations.Select(m => m.SetId));
_eqpManipulations.Clear(); _eqpManipulations.Clear();
} }

View file

@ -29,7 +29,7 @@ public struct GmpCache : IDisposable
if( _gmpFile == null ) if( _gmpFile == null )
return; return;
_gmpFile.Reset( _gmpManipulations.Select( m => ( int )m.SetId ) ); _gmpFile.Reset( _gmpManipulations.Select( m => m.SetId ) );
_gmpManipulations.Clear(); _gmpManipulations.Clear();
} }

View file

@ -99,7 +99,7 @@ public readonly struct ImcCache : IDisposable
return true; return true;
} }
var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant, out _); var def = ImcFile.GetDefault(manager, file.Path, m.EquipSlot, m.Variant.Id, out _);
var manip = m.Copy(def); var manip = m.Copy(def);
if (!manip.Apply(file)) if (!manip.Apply(file))
return false; return false;

View file

@ -59,7 +59,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
if (!_config.UseOwnerNameForCharacterCollection) if (!_config.UseOwnerNameForCharacterCollection)
return false; return false;
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, identifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
ObjectKind.None, uint.MaxValue); ObjectKind.None, uint.MaxValue);
return CheckWorlds(identifier, out collection); return CheckWorlds(identifier, out collection);
} }

View file

@ -135,7 +135,7 @@ public sealed partial class IndividualCollections
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
return table.Where(kvp => kvp.Value == name) return table.Where(kvp => kvp.Value == name)
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind, .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, identifier.Kind,
kvp.Key)).ToArray(); kvp.Key)).ToArray();
} }

View file

@ -118,7 +118,7 @@ public partial class TexToolsMeta
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0. var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
foreach (var value in values) foreach (var value in values)
{ {
if (_keepDefault || !value.Equals(def.GetEntry(partIdx, i))) if (_keepDefault || !value.Equals(def.GetEntry(partIdx, (Variant) i)))
{ {
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
value); value);

View file

@ -152,7 +152,7 @@ public partial class TexToolsMeta
for( var i = 0; i <= baseFile.Count; ++i ) for( var i = 0; i <= baseFile.Count; ++i )
{ {
var entry = baseFile.GetEntry( partIdx, i ); var entry = baseFile.GetEntry( partIdx, (Variant)i );
b.Write( entry.MaterialId ); b.Write( entry.MaterialId );
b.Write( entry.DecalId ); b.Write( entry.DecalId );
b.Write( entry.AttributeAndSound ); b.Write( entry.AttributeAndSound );
@ -184,7 +184,7 @@ public partial class TexToolsMeta
foreach( var manip in manips ) foreach( var manip in manips )
{ {
b.Write( ( ushort )Names.CombinedRace( manip.Est.Gender, manip.Est.Race ) ); b.Write( ( ushort )Names.CombinedRace( manip.Est.Gender, manip.Est.Race ) );
b.Write( manip.Est.SetId ); b.Write( manip.Est.SetId.Id );
b.Write( manip.Est.Entry ); b.Write( manip.Est.Entry );
} }

View file

@ -244,7 +244,7 @@ public unsafe class CollectionResolver
if (identifier.Type != IdentifierType.Owned || !_config.UseOwnerNameForCharacterCollection || owner == null) if (identifier.Type != IdentifierType.Owned || !_config.UseOwnerNameForCharacterCollection || owner == null)
return null; return null;
var id = _actors.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, var id = _actors.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
ObjectKind.None, ObjectKind.None,
uint.MaxValue); uint.MaxValue);
return CheckYourself(id, owner) return CheckYourself(id, owner)

View file

@ -225,10 +225,10 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
{ {
"chara" => SafeGet(path, 1) switch "chara" => SafeGet(path, 1) switch
{ {
"accessory" => IsMatchEquipment(path[2..], $"a{Equipment.Set.Value:D4}"), "accessory" => IsMatchEquipment(path[2..], $"a{Equipment.Set.Id:D4}"),
"equipment" => IsMatchEquipment(path[2..], $"e{Equipment.Set.Value:D4}"), "equipment" => IsMatchEquipment(path[2..], $"e{Equipment.Set.Id:D4}"),
"monster" => SafeGet(path, 2) == $"m{Skeleton:D4}", "monster" => SafeGet(path, 2) == $"m{Skeleton:D4}",
"weapon" => IsMatchEquipment(path[2..], $"w{Equipment.Set.Value:D4}"), "weapon" => IsMatchEquipment(path[2..], $"w{Equipment.Set.Id:D4}"),
_ => null, _ => null,
}, },
_ => null, _ => null,
@ -238,7 +238,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
=> SafeGet(path, 0) == equipmentDir => SafeGet(path, 0) == equipmentDir
? SafeGet(path, 1) switch ? SafeGet(path, 1) switch
{ {
"material" => SafeGet(path, 2) == $"v{Equipment.Variant:D4}", "material" => SafeGet(path, 2) == $"v{Equipment.Variant.Id:D4}",
_ => null, _ => null,
} }
: false; : false;

View file

@ -40,21 +40,21 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public int Count public int Count
=> (Length - DataOffset) / EqdpEntrySize; => (Length - DataOffset) / EqdpEntrySize;
public EqdpEntry this[int idx] public EqdpEntry this[SetId id]
{ {
get get
{ {
if (idx >= Count || idx < 0) if (id.Id >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * idx)); return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * id.Id));
} }
set set
{ {
if (idx >= Count || idx < 0) if (id.Id >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
*(ushort*)(Data + DataOffset + EqdpEntrySize * idx) = (ushort)value; *(ushort*)(Data + DataOffset + EqdpEntrySize * id.Id) = (ushort)value;
} }
} }
@ -81,7 +81,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data)); MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
} }
public void Reset(IEnumerable<int> entries) public void Reset(IEnumerable<SetId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(entry); this[entry] = GetDefault(entry);
@ -103,18 +103,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
Reset(); Reset();
} }
public EqdpEntry GetDefault(int setIdx) public EqdpEntry GetDefault(SetId setId)
=> GetDefault(Manager, Index, setIdx); => GetDefault(Manager, Index, setId);
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, int setIdx) public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, SetId setId)
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setIdx); => GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setId);
public static EqdpEntry GetDefault(byte* data, int setIdx) public static EqdpEntry GetDefault(byte* data, SetId setId)
{ {
var blockSize = *(ushort*)(data + IdentifierSize); var blockSize = *(ushort*)(data + IdentifierSize);
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2); var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
var blockIdx = setIdx / blockSize; var blockIdx = setId.Id / blockSize;
if (blockIdx >= totalBlockCount) if (blockIdx >= totalBlockCount)
return 0; return 0;
@ -123,9 +123,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
return 0; return 0;
var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2); var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2);
return (EqdpEntry)(*(blockData + setIdx % blockSize)); return (EqdpEntry)(*(blockData + setId.Id % blockSize));
} }
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, int setIdx) public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, SetId setId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setIdx); => GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setId);
} }

View file

@ -28,26 +28,26 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public ulong ControlBlock public ulong ControlBlock
=> *(ulong*)Data; => *(ulong*)Data;
protected ulong GetInternal(int idx) protected ulong GetInternal(SetId idx)
{ {
return idx switch return idx.Id switch
{ {
>= Count => throw new IndexOutOfRangeException(), >= Count => throw new IndexOutOfRangeException(),
<= 1 => *((ulong*)Data + 1), <= 1 => *((ulong*)Data + 1),
_ => *((ulong*)Data + idx), _ => *((ulong*)Data + idx.Id),
}; };
} }
protected void SetInternal(int idx, ulong value) protected void SetInternal(SetId idx, ulong value)
{ {
idx = idx switch idx = idx.Id switch
{ {
>= Count => throw new IndexOutOfRangeException(), >= Count => throw new IndexOutOfRangeException(),
<= 0 => 1, <= 0 => 1,
_ => idx, _ => idx,
}; };
*((ulong*)Data + idx) = value; *((ulong*)Data + idx.Id) = value;
} }
protected virtual void SetEmptyBlock(int idx) protected virtual void SetEmptyBlock(int idx)
@ -85,13 +85,13 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
Reset(); Reset();
} }
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def) protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, SetId setId, ulong def)
{ {
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address; var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
if (setIdx == 0) if (setId == 0)
setIdx = 1; setId = 1;
var blockIdx = setIdx / BlockSize; var blockIdx = setId.Id / BlockSize;
if (blockIdx >= NumBlocks) if (blockIdx >= NumBlocks)
return def; return def;
@ -101,7 +101,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
return def; return def;
var count = BitOperations.PopCount(control & (blockBit - 1)); var count = BitOperations.PopCount(control & (blockBit - 1));
var idx = setIdx % BlockSize; var idx = setId.Id % BlockSize;
var ptr = (ulong*)data + BlockSize * count + idx; var ptr = (ulong*)data + BlockSize * count + idx;
return *ptr; return *ptr;
} }
@ -116,14 +116,14 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
: base(manager, false) : base(manager, false)
{ } { }
public EqpEntry this[int idx] public EqpEntry this[SetId idx]
{ {
get => (EqpEntry)GetInternal(idx); get => (EqpEntry)GetInternal(idx);
set => SetInternal(idx, (ulong)value); set => SetInternal(idx, (ulong)value);
} }
public static EqpEntry GetDefault(MetaFileManager manager, int setIdx) public static EqpEntry GetDefault(MetaFileManager manager, SetId setIdx)
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry); => (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry);
protected override unsafe void SetEmptyBlock(int idx) protected override unsafe void SetEmptyBlock(int idx)
@ -134,7 +134,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
*ptr = (ulong)Eqp.DefaultEntry; *ptr = (ulong)Eqp.DefaultEntry;
} }
public void Reset(IEnumerable<int> entries) public void Reset(IEnumerable<SetId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry); this[entry] = GetDefault(Manager, entry);
@ -142,7 +142,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
public IEnumerator<EqpEntry> GetEnumerator() public IEnumerator<EqpEntry> GetEnumerator()
{ {
for (var idx = 1; idx < Count; ++idx) for (ushort idx = 1; idx < Count; ++idx)
yield return this[idx]; yield return this[idx];
} }
@ -159,16 +159,16 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
: base(manager, true) : base(manager, true)
{ } { }
public GmpEntry this[int idx] public GmpEntry this[SetId idx]
{ {
get => (GmpEntry)GetInternal(idx); get => (GmpEntry)GetInternal(idx);
set => SetInternal(idx, (ulong)value); set => SetInternal(idx, (ulong)value);
} }
public static GmpEntry GetDefault(MetaFileManager manager, int setIdx) public static GmpEntry GetDefault(MetaFileManager manager, SetId setIdx)
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default); => (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default);
public void Reset(IEnumerable<int> entries) public void Reset(IEnumerable<SetId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry); this[entry] = GetDefault(Manager, entry);
@ -176,7 +176,7 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
public IEnumerator<GmpEntry> GetEnumerator() public IEnumerator<GmpEntry> GetEnumerator()
{ {
for (var idx = 1; idx < Count; ++idx) for (ushort idx = 1; idx < Count; ++idx)
yield return this[idx]; yield return this[idx];
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
@ -168,21 +169,21 @@ public sealed unsafe class EstFile : MetaBaseFile
public ushort GetDefault(GenderRace genderRace, ushort setId) public ushort GetDefault(GenderRace genderRace, ushort setId)
=> GetDefault(Manager, Index, genderRace, setId); => GetDefault(Manager, Index, genderRace, setId);
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId) public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, SetId setId)
{ {
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address; var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
var count = *(int*)data; var count = *(int*)data;
var span = new ReadOnlySpan<Info>(data + 4, count); var span = new ReadOnlySpan<Info>(data + 4, count);
var (idx, found) = FindEntry(span, genderRace, setId); var (idx, found) = FindEntry(span, genderRace, setId.Id);
if (!found) if (!found)
return 0; return 0;
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize); return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
} }
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, ushort setId) public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, SetId setId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId); => GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId) public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, SetId setId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId); => GetDefault(manager, (MetaIndex)estType, genderRace, setId);
} }

View file

@ -4,7 +4,6 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Services;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.String.Functions; using Penumbra.String.Functions;
@ -50,19 +49,19 @@ public unsafe class ImcFile : MetaBaseFile
private static ushort PartMask(byte* data) private static ushort PartMask(byte* data)
=> *(ushort*)(data + 2); => *(ushort*)(data + 2);
private static ImcEntry* VariantPtr(byte* data, int partIdx, int variantIdx) private static ImcEntry* VariantPtr(byte* data, int partIdx, Variant variantIdx)
{ {
var flag = 1 << partIdx; var flag = 1 << partIdx;
if ((PartMask(data) & flag) == 0 || variantIdx > CountInternal(data)) if ((PartMask(data) & flag) == 0 || variantIdx.Id > CountInternal(data))
return null; return null;
var numParts = BitOperations.PopCount(PartMask(data)); var numParts = BitOperations.PopCount(PartMask(data));
var ptr = (ImcEntry*)(data + PreambleSize); var ptr = (ImcEntry*)(data + PreambleSize);
ptr += variantIdx * numParts + partIdx; ptr += variantIdx.Id * numParts + partIdx;
return ptr; return ptr;
} }
public ImcEntry GetEntry(int partIdx, int variantIdx) public ImcEntry GetEntry(int partIdx, Variant variantIdx)
{ {
var ptr = VariantPtr(Data, partIdx, variantIdx); var ptr = VariantPtr(Data, partIdx, variantIdx);
return ptr == null ? new ImcEntry() : *ptr; return ptr == null ? new ImcEntry() : *ptr;
@ -106,12 +105,12 @@ public unsafe class ImcFile : MetaBaseFile
return true; return true;
} }
public bool SetEntry(int partIdx, int variantIdx, ImcEntry entry) public bool SetEntry(int partIdx, Variant variantIdx, ImcEntry entry)
{ {
if (partIdx >= NumParts) if (partIdx >= NumParts)
return false; return false;
EnsureVariantCount(variantIdx); EnsureVariantCount(variantIdx.Id);
var variantPtr = VariantPtr(Data, partIdx, variantIdx); var variantPtr = VariantPtr(Data, partIdx, variantIdx);
if (variantPtr == null) if (variantPtr == null)
@ -154,10 +153,10 @@ public unsafe class ImcFile : MetaBaseFile
} }
} }
public static ImcEntry GetDefault(MetaFileManager manager, Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists) public static ImcEntry GetDefault(MetaFileManager manager, Utf8GamePath path, EquipSlot slot, Variant variantIdx, out bool exists)
=> GetDefault(manager, path.ToString(), slot, variantIdx, out exists); => GetDefault(manager, path.ToString(), slot, variantIdx, out exists);
public static ImcEntry GetDefault(MetaFileManager manager, string path, EquipSlot slot, int variantIdx, out bool exists) public static ImcEntry GetDefault(MetaFileManager manager, string path, EquipSlot slot, Variant variantIdx, out bool exists)
{ {
var file = manager.GameData.GetFile(path); var file = manager.GameData.GetFile(path);
exists = false; exists = false;

View file

@ -20,13 +20,13 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public ModelRace Race { get; private init; } public ModelRace Race { get; private init; }
public ushort SetId { get; private init; } public SetId SetId { get; private init; }
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public EquipSlot Slot { get; private init; } public EquipSlot Slot { get; private init; }
[JsonConstructor] [JsonConstructor]
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId) public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, SetId setId)
{ {
Gender = gender; Gender = gender;
Race = race; Race = race;
@ -74,7 +74,7 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
if (g != 0) if (g != 0)
return g; return g;
var set = SetId.CompareTo(other.SetId); var set = SetId.Id.CompareTo(other.SetId.Id);
return set != 0 ? set : Slot.CompareTo(other.Slot); return set != 0 ? set : Slot.CompareTo(other.Slot);
} }

View file

@ -17,13 +17,13 @@ public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
[JsonConverter( typeof( ForceNumericFlagEnumConverter ) )] [JsonConverter( typeof( ForceNumericFlagEnumConverter ) )]
public EqpEntry Entry { get; private init; } public EqpEntry Entry { get; private init; }
public ushort SetId { get; private init; } public SetId SetId { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter( typeof( StringEnumConverter ) )]
public EquipSlot Slot { get; private init; } public EquipSlot Slot { get; private init; }
[JsonConstructor] [JsonConstructor]
public EqpManipulation( EqpEntry entry, EquipSlot slot, ushort setId ) public EqpManipulation( EqpEntry entry, EquipSlot slot, SetId setId )
{ {
Slot = slot; Slot = slot;
SetId = setId; SetId = setId;
@ -48,7 +48,7 @@ public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
public int CompareTo( EqpManipulation other ) public int CompareTo( EqpManipulation other )
{ {
var set = SetId.CompareTo( other.SetId ); var set = SetId.Id.CompareTo( other.SetId.Id );
return set != 0 ? set : Slot.CompareTo( other.Slot ); return set != 0 ? set : Slot.CompareTo( other.Slot );
} }

View file

@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
@ -37,13 +38,13 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter( typeof( StringEnumConverter ) )]
public ModelRace Race { get; private init; } public ModelRace Race { get; private init; }
public ushort SetId { get; private init; } public SetId SetId { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter( typeof( StringEnumConverter ) )]
public EstType Slot { get; private init; } public EstType Slot { get; private init; }
[JsonConstructor] [JsonConstructor]
public EstManipulation( Gender gender, ModelRace race, EstType slot, ushort setId, ushort entry ) public EstManipulation( Gender gender, ModelRace race, EstType slot, SetId setId, ushort entry )
{ {
Entry = entry; Entry = entry;
Gender = gender; Gender = gender;
@ -86,7 +87,7 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
} }
var s = Slot.CompareTo( other.Slot ); var s = Slot.CompareTo( other.Slot );
return s != 0 ? s : SetId.CompareTo( other.SetId ); return s != 0 ? s : SetId.Id.CompareTo( other.SetId.Id );
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
@ -94,7 +95,7 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
public bool Apply( EstFile file ) public bool Apply( EstFile file )
{ {
return file.SetEntry( Names.CombinedRace( Gender, Race ), SetId, Entry ) switch return file.SetEntry( Names.CombinedRace( Gender, Race ), SetId.Id, Entry ) switch
{ {
EstFile.EstEntryChange.Unchanged => false, EstFile.EstEntryChange.Unchanged => false,
EstFile.EstEntryChange.Changed => true, EstFile.EstEntryChange.Changed => true,

View file

@ -10,10 +10,10 @@ namespace Penumbra.Meta.Manipulations;
public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation > public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
{ {
public GmpEntry Entry { get; private init; } public GmpEntry Entry { get; private init; }
public ushort SetId { get; private init; } public SetId SetId { get; private init; }
[JsonConstructor] [JsonConstructor]
public GmpManipulation( GmpEntry entry, ushort setId ) public GmpManipulation( GmpEntry entry, SetId setId )
{ {
Entry = entry; Entry = entry;
SetId = setId; SetId = setId;
@ -35,7 +35,7 @@ public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
=> SetId.GetHashCode(); => SetId.GetHashCode();
public int CompareTo( GmpManipulation other ) public int CompareTo( GmpManipulation other )
=> SetId.CompareTo( other.SetId ); => SetId.Id.CompareTo( other.SetId.Id );
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> MetaIndex.Gmp; => MetaIndex.Gmp;

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Reflection.Metadata;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@ -12,28 +11,28 @@ using Penumbra.String.Classes;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation > public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
{ {
public ImcEntry Entry { get; private init; } public ImcEntry Entry { get; private init; }
public ushort PrimaryId { get; private init; } public SetId PrimaryId { get; private init; }
public ushort SecondaryId { get; private init; } public SetId SecondaryId { get; private init; }
public byte Variant { get; private init; } public Variant Variant { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public ObjectType ObjectType { get; private init; } public ObjectType ObjectType { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public EquipSlot EquipSlot { get; private init; } public EquipSlot EquipSlot { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public BodySlot BodySlot { get; private init; } public BodySlot BodySlot { get; private init; }
public ImcManipulation( EquipSlot equipSlot, ushort variant, ushort primaryId, ImcEntry entry ) public ImcManipulation(EquipSlot equipSlot, ushort variant, SetId primaryId, ImcEntry entry)
{ {
Entry = entry; Entry = entry;
PrimaryId = primaryId; PrimaryId = primaryId;
Variant = ( byte )Math.Clamp( variant, ( ushort )0, byte.MaxValue ); Variant = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
SecondaryId = 0; SecondaryId = 0;
ObjectType = equipSlot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment; ObjectType = equipSlot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment;
EquipSlot = equipSlot; EquipSlot = equipSlot;
@ -45,21 +44,21 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
// so we change the unused value to something nonsensical in that case, just so they do not compare equal, // so we change the unused value to something nonsensical in that case, just so they do not compare equal,
// and clamp the variant to 255. // and clamp the variant to 255.
[JsonConstructor] [JsonConstructor]
internal ImcManipulation( ObjectType objectType, BodySlot bodySlot, ushort primaryId, ushort secondaryId, ushort variant, internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, SetId primaryId, SetId secondaryId, ushort variant,
EquipSlot equipSlot, ImcEntry entry ) EquipSlot equipSlot, ImcEntry entry)
{ {
Entry = entry; Entry = entry;
ObjectType = objectType; ObjectType = objectType;
PrimaryId = primaryId; PrimaryId = primaryId;
Variant = ( byte )Math.Clamp( variant, ( ushort )0, byte.MaxValue ); Variant = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
if( objectType is ObjectType.Accessory or ObjectType.Equipment ) if (objectType is ObjectType.Accessory or ObjectType.Equipment)
{ {
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown; BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
SecondaryId = 0; SecondaryId = 0;
EquipSlot = equipSlot; EquipSlot = equipSlot;
} }
else if( objectType is ObjectType.DemiHuman ) else if (objectType is ObjectType.DemiHuman)
{ {
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown; BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
SecondaryId = secondaryId; SecondaryId = secondaryId;
@ -73,85 +72,81 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
} }
} }
public ImcManipulation Copy( ImcEntry entry ) public ImcManipulation Copy(ImcEntry entry)
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant, EquipSlot, entry); => new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant.Id, EquipSlot, entry);
public override string ToString() public override string ToString()
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory => ObjectType is ObjectType.Equipment or ObjectType.Accessory
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}" ? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
: $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}"; : $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}";
public bool Equals( ImcManipulation other ) public bool Equals(ImcManipulation other)
=> PrimaryId == other.PrimaryId => PrimaryId == other.PrimaryId
&& Variant == other.Variant && Variant == other.Variant
&& SecondaryId == other.SecondaryId && SecondaryId == other.SecondaryId
&& ObjectType == other.ObjectType && ObjectType == other.ObjectType
&& EquipSlot == other.EquipSlot && EquipSlot == other.EquipSlot
&& BodySlot == other.BodySlot; && BodySlot == other.BodySlot;
public override bool Equals( object? obj ) public override bool Equals(object? obj)
=> obj is ImcManipulation other && Equals( other ); => obj is ImcManipulation other && Equals(other);
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine( PrimaryId, Variant, SecondaryId, ( int )ObjectType, ( int )EquipSlot, ( int )BodySlot ); => HashCode.Combine(PrimaryId, Variant, SecondaryId, (int)ObjectType, (int)EquipSlot, (int)BodySlot);
public int CompareTo( ImcManipulation other ) public int CompareTo(ImcManipulation other)
{ {
var o = ObjectType.CompareTo( other.ObjectType ); var o = ObjectType.CompareTo(other.ObjectType);
if( o != 0 ) if (o != 0)
{
return o; return o;
}
var i = PrimaryId.CompareTo( other.PrimaryId ); var i = PrimaryId.Id.CompareTo(other.PrimaryId.Id);
if( i != 0 ) if (i != 0)
{
return i; return i;
if (ObjectType is ObjectType.Equipment or ObjectType.Accessory)
{
var e = EquipSlot.CompareTo(other.EquipSlot);
return e != 0 ? e : Variant.Id.CompareTo(other.Variant.Id);
} }
if( ObjectType is ObjectType.Equipment or ObjectType.Accessory ) if (ObjectType is ObjectType.DemiHuman)
{ {
var e = EquipSlot.CompareTo( other.EquipSlot ); var e = EquipSlot.CompareTo(other.EquipSlot);
return e != 0 ? e : Variant.CompareTo( other.Variant ); if (e != 0)
}
if( ObjectType is ObjectType.DemiHuman )
{
var e = EquipSlot.CompareTo( other.EquipSlot );
if( e != 0 )
{
return e; return e;
}
} }
var s = SecondaryId.CompareTo( other.SecondaryId ); var s = SecondaryId.Id.CompareTo(other.SecondaryId.Id);
if( s != 0 ) if (s != 0)
{
return s; return s;
}
var b = BodySlot.CompareTo( other.BodySlot ); var b = BodySlot.CompareTo(other.BodySlot);
return b != 0 ? b : Variant.CompareTo( other.Variant ); return b != 0 ? b : Variant.Id.CompareTo(other.Variant.Id);
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> ( MetaIndex )( -1 ); => (MetaIndex)(-1);
public Utf8GamePath GamePath() public Utf8GamePath GamePath()
{ {
return ObjectType switch return ObjectType switch
{ {
ObjectType.Accessory => Utf8GamePath.FromString( GamePaths.Accessory.Imc.Path( PrimaryId ), out var p ) ? p : Utf8GamePath.Empty, ObjectType.Accessory => Utf8GamePath.FromString(GamePaths.Accessory.Imc.Path(PrimaryId), out var p) ? p : Utf8GamePath.Empty,
ObjectType.Equipment => Utf8GamePath.FromString( GamePaths.Equipment.Imc.Path( PrimaryId ), out var p ) ? p : Utf8GamePath.Empty, ObjectType.Equipment => Utf8GamePath.FromString(GamePaths.Equipment.Imc.Path(PrimaryId), out var p) ? p : Utf8GamePath.Empty,
ObjectType.DemiHuman => Utf8GamePath.FromString( GamePaths.DemiHuman.Imc.Path( PrimaryId, SecondaryId ), out var p ) ? p : Utf8GamePath.Empty, ObjectType.DemiHuman => Utf8GamePath.FromString(GamePaths.DemiHuman.Imc.Path(PrimaryId, SecondaryId), out var p)
ObjectType.Monster => Utf8GamePath.FromString( GamePaths.Monster.Imc.Path( PrimaryId, SecondaryId ), out var p ) ? p : Utf8GamePath.Empty, ? p
ObjectType.Weapon => Utf8GamePath.FromString( GamePaths.Weapon.Imc.Path( PrimaryId, SecondaryId ), out var p ) ? p : Utf8GamePath.Empty, : Utf8GamePath.Empty,
_ => throw new NotImplementedException(), ObjectType.Monster => Utf8GamePath.FromString(GamePaths.Monster.Imc.Path(PrimaryId, SecondaryId), out var p)
? p
: Utf8GamePath.Empty,
ObjectType.Weapon => Utf8GamePath.FromString(GamePaths.Weapon.Imc.Path(PrimaryId, SecondaryId), out var p) ? p : Utf8GamePath.Empty,
_ => throw new NotImplementedException(),
}; };
} }
public bool Apply( ImcFile file ) public bool Apply(ImcFile file)
=> file.SetEntry( ImcFile.PartIndex( EquipSlot ), Variant, Entry ); => file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
public bool Validate() public bool Validate()
{ {
@ -165,12 +160,14 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
return false; return false;
if (SecondaryId != 0) if (SecondaryId != 0)
return false; return false;
break; break;
case ObjectType.DemiHuman: case ObjectType.DemiHuman:
if (BodySlot is not BodySlot.Unknown) if (BodySlot is not BodySlot.Unknown)
return false; return false;
if (!EquipSlot.IsEquipment() && !EquipSlot.IsAccessory()) if (!EquipSlot.IsEquipment() && !EquipSlot.IsAccessory())
return false; return false;
break; break;
default: default:
if (!Enum.IsDefined(BodySlot)) if (!Enum.IsDefined(BodySlot))
@ -179,6 +176,7 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
return false; return false;
if (!Enum.IsDefined(ObjectType)) if (!Enum.IsDefined(ObjectType))
return false; return false;
break; break;
} }
@ -187,4 +185,4 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
return true; return true;
} }
} }

View file

@ -15,7 +15,7 @@ public static class CustomizationSwap
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode. /// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
public static FileSwap CreateMdl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo ) public static FileSwap CreateMdl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo )
{ {
if( idFrom.Value > byte.MaxValue ) if( idFrom.Id > byte.MaxValue )
{ {
throw new Exception( $"The Customization ID {idFrom} is too large for {slot}." ); throw new Exception( $"The Customization ID {idFrom} is too large for {slot}." );
} }
@ -51,9 +51,9 @@ public static class CustomizationSwap
var newFileName = fileName; var newFileName = fileName;
newFileName = ItemSwap.ReplaceRace( newFileName, gameRaceTo, race, gameRaceTo != race ); newFileName = ItemSwap.ReplaceRace( newFileName, gameRaceTo, race, gameRaceTo != race );
newFileName = ItemSwap.ReplaceBody( newFileName, slot, idTo, idFrom, idFrom.Value != idTo.Value ); newFileName = ItemSwap.ReplaceBody( newFileName, slot, idTo, idFrom, idFrom != idTo );
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_c{race.ToRaceCode()}", gameRaceFrom != race || MaterialHandling.IsSpecialCase( race, idFrom ) ); newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_c{race.ToRaceCode()}", gameRaceFrom != race || MaterialHandling.IsSpecialCase( race, idFrom ) );
newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Value:D4}", gameSetIdFrom.Value != idFrom.Value ); newFileName = ItemSwap.AddSuffix( newFileName, ".mtrl", $"_{slot.ToAbbreviation()}{idFrom.Id:D4}", gameSetIdFrom != idFrom );
var actualMtrlFromPath = mtrlFromPath; var actualMtrlFromPath = mtrlFromPath;
if( newFileName != fileName ) if( newFileName != fileName )

View file

@ -54,11 +54,11 @@ public static class EquipmentSwap
throw new ItemSwap.InvalidItemTypeException(); throw new ItemSwap.InvalidItemTypeException();
var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom); var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom);
var imcManip = new ImcManipulation(slotTo, variantTo, idTo.Value, default); var imcManip = new ImcManipulation(slotTo, variantTo.Id, idTo.Id, default);
var imcFileTo = new ImcFile(manager, imcManip); var imcFileTo = new ImcFile(manager, imcManip);
var skipFemale = false; var skipFemale = false;
var skipMale = false; var skipMale = false;
var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo))).Imc.Entry.MaterialId; var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo.Id))).Imc.Entry.MaterialId;
foreach (var gr in Enum.GetValues<GenderRace>()) foreach (var gr in Enum.GetValues<GenderRace>())
{ {
switch (gr.Split().Item1) switch (gr.Split().Item1)
@ -124,7 +124,7 @@ public static class EquipmentSwap
foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger)) foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger))
{ {
(var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); (var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom);
var imcManip = new ImcManipulation(slot, variantTo, idTo.Value, default); var imcManip = new ImcManipulation(slot, variantTo.Id, idTo, default);
var imcFileTo = new ImcFile(manager, imcManip); var imcFileTo = new ImcFile(manager, imcManip);
var isAccessory = slot.IsAccessory(); var isAccessory = slot.IsAccessory();
@ -198,10 +198,10 @@ public static class EquipmentSwap
SetId idTo, byte mtrlTo) SetId idTo, byte mtrlTo)
{ {
var (gender, race) = gr.Split(); var (gender, race) = gr.Split();
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom.Value), slotFrom, gender, var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender,
race, idFrom.Value); race, idFrom);
var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo.Value), slotTo, gender, race, var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo), slotTo, gender, race,
idTo.Value); idTo);
var meta = new MetaSwap(manips, eqdpFrom, eqdpTo); var meta = new MetaSwap(manips, eqdpFrom, eqdpTo);
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom); var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom);
if (ownMdl) if (ownMdl)
@ -240,7 +240,7 @@ public static class EquipmentSwap
return mdl; return mdl;
} }
private static void LookupItem(EquipItem i, out EquipSlot slot, out SetId modelId, out byte variant) private static void LookupItem(EquipItem i, out EquipSlot slot, out SetId modelId, out Variant variant)
{ {
slot = i.Type.ToSlot(); slot = i.Type.ToSlot();
if (!slot.IsEquipmentPiece()) if (!slot.IsEquipmentPiece())
@ -250,14 +250,14 @@ public static class EquipmentSwap
variant = i.Variant; variant = i.Variant;
} }
private static (ImcFile, byte[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom, private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
SetId idFrom, SetId idTo, byte variantFrom) SetId idFrom, SetId idTo, Variant variantFrom)
{ {
var entry = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, default); var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry); var imc = new ImcFile(manager, entry);
EquipItem[] items; EquipItem[] items;
byte[] variants; Variant[] variants;
if (idFrom.Value == idTo.Value) if (idFrom == idTo)
{ {
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray(); items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
variants = new[] variants = new[]
@ -270,7 +270,7 @@ public static class EquipmentSwap
items = identifier.Identify(slotFrom.IsEquipment() items = identifier.Identify(slotFrom.IsEquipment()
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom) ? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray(); : GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<EquipItem>().ToArray();
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (byte)i).ToArray(); variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray();
} }
return (imc, variants, items); return (imc, variants, items);
@ -282,24 +282,23 @@ public static class EquipmentSwap
if (slot is not EquipSlot.Head) if (slot is not EquipSlot.Head)
return null; return null;
var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom.Value), idFrom.Value); var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom), idFrom);
var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo.Value), idTo.Value); var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo), idTo);
return new MetaSwap(manips, manipFrom, manipTo); return new MetaSwap(manips, manipFrom, manipTo);
} }
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
SetId idFrom, SetId idTo, SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo); => CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips,
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
{ {
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo);
var manipulationFrom = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, entryFrom); var manipulationFrom = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, entryFrom);
var manipulationTo = new ImcManipulation(slotTo, variantTo, idTo.Value, entryTo); var manipulationTo = new ImcManipulation(slotTo, variantTo.Id, idTo, entryTo);
var imc = new MetaSwap(manips, manipulationFrom, manipulationTo); var imc = new MetaSwap(manips, manipulationFrom, manipulationTo);
var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId); var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId);
@ -332,8 +331,8 @@ public static class EquipmentSwap
if (vfxId == 0) if (vfxId == 0)
return null; return null;
var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom.Value, vfxId); var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom, vfxId);
var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo.Value, vfxId); var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId);
var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo);
foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan()) foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan())
@ -351,10 +350,10 @@ public static class EquipmentSwap
if (slot.IsAccessory()) if (slot.IsAccessory())
return null; return null;
var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom.Value); var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom);
var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo.Value); var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo);
var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom.Value); var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom);
var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom.Value); var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom);
return new MetaSwap(manips, eqpFrom, eqpTo); return new MetaSwap(manips, eqpFrom, eqpTo);
} }
@ -368,7 +367,7 @@ public static class EquipmentSwap
ref bool dataWasChanged) ref bool dataWasChanged)
{ {
var prefix = slotTo.IsAccessory() ? 'a' : 'e'; var prefix = slotTo.IsAccessory() ? 'a' : 'e';
if (!fileName.Contains($"{prefix}{idTo.Value:D4}")) if (!fileName.Contains($"{prefix}{idTo.Id:D4}"))
return null; return null;
var folderTo = slotTo.IsAccessory() var folderTo = slotTo.IsAccessory()

View file

@ -9,7 +9,6 @@ using Penumbra.GameData.Structs;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Services;
using Penumbra.String.Classes; using Penumbra.String.Classes;
namespace Penumbra.Mods.ItemSwap; namespace Penumbra.Mods.ItemSwap;
@ -163,8 +162,8 @@ public static class ItemSwap
} }
var (gender, race) = genderRace.Split(); var (gender, race) = genderRace.Split();
var fromDefault = new EstManipulation( gender, race, type, idFrom.Value, EstFile.GetDefault( manager, type, genderRace, idFrom.Value ) ); var fromDefault = new EstManipulation( gender, race, type, idFrom, EstFile.GetDefault( manager, type, genderRace, idFrom ) );
var toDefault = new EstManipulation( gender, race, type, idTo.Value, EstFile.GetDefault( manager, type, genderRace, idTo.Value ) ); var toDefault = new EstManipulation( gender, race, type, idTo, EstFile.GetDefault( manager, type, genderRace, idTo ) );
var est = new MetaSwap( manips, fromDefault, toDefault ); var est = new MetaSwap( manips, fromDefault, toDefault );
if( ownMdl && est.SwapApplied.Est.Entry >= 2 ) if( ownMdl && est.SwapApplied.Est.Entry >= 2 )
@ -206,7 +205,7 @@ public static class ItemSwap
public static string ReplaceAnyId( string path, char idType, SetId id, bool condition = true ) public static string ReplaceAnyId( string path, char idType, SetId id, bool condition = true )
=> condition => condition
? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Value:D4}" ) ? Regex.Replace( path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}" )
: path; : path;
public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true ) public static string ReplaceAnyRace( string path, GenderRace to, bool condition = true )
@ -217,7 +216,7 @@ public static class ItemSwap
public static string ReplaceId( string path, char type, SetId idFrom, SetId idTo, bool condition = true ) public static string ReplaceId( string path, char type, SetId idFrom, SetId idTo, bool condition = true )
=> condition => condition
? path.Replace( $"{type}{idFrom.Value:D4}", $"{type}{idTo.Value:D4}" ) ? path.Replace( $"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}" )
: path; : path;
public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true ) public static string ReplaceSlot( string path, EquipSlot from, EquipSlot to, bool condition = true )

View file

@ -135,7 +135,7 @@ public partial class ModEditWindow
// Identifier // Identifier
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##eqpId", IdWidth, _new.SetId, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) if (IdInput("##eqpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
_new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), _new.Slot, setId); _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), _new.Slot, setId);
ImGuiUtil.HoverTooltip(ModelSetIdTooltip); ImGuiUtil.HoverTooltip(ModelSetIdTooltip);
@ -224,7 +224,7 @@ public partial class ModEditWindow
// Identifier // Identifier
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##eqdpId", IdWidth, _new.SetId, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) if (IdInput("##eqdpId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
{ {
var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), setId); var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), setId);
_new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId); _new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId);
@ -352,14 +352,14 @@ public partial class ModEditWindow
_ => EquipSlot.Unknown, _ => EquipSlot.Unknown,
}; };
_new = new ImcManipulation(type, _new.BodySlot, _new.PrimaryId, _new.SecondaryId == 0 ? (ushort)1 : _new.SecondaryId, _new = new ImcManipulation(type, _new.BodySlot, _new.PrimaryId, _new.SecondaryId == 0 ? (ushort)1 : _new.SecondaryId,
_new.Variant, equipSlot, _new.Entry); _new.Variant.Id, equipSlot, _new.Entry);
} }
ImGuiUtil.HoverTooltip(ObjectTypeTooltip); ImGuiUtil.HoverTooltip(ObjectTypeTooltip);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##imcId", IdWidth, _new.PrimaryId, out var setId, 0, ushort.MaxValue, _new.PrimaryId <= 1)) if (IdInput("##imcId", IdWidth, _new.PrimaryId.Id, out var setId, 0, ushort.MaxValue, _new.PrimaryId <= 1))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, setId, _new.SecondaryId, _new.Variant, _new.EquipSlot, _new.Entry) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, setId, _new.SecondaryId, _new.Variant.Id, _new.EquipSlot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new) .Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -373,7 +373,7 @@ public partial class ModEditWindow
if (_new.ObjectType is ObjectType.Equipment) if (_new.ObjectType is ObjectType.Equipment)
{ {
if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot)) if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new) .Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -382,7 +382,7 @@ public partial class ModEditWindow
else if (_new.ObjectType is ObjectType.Accessory) else if (_new.ObjectType is ObjectType.Accessory)
{ {
if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot)) if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new) .Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -390,8 +390,8 @@ public partial class ModEditWindow
} }
else else
{ {
if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId, out var setId2, 0, ushort.MaxValue, false)) if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId.Id, out var setId2, 0, ushort.MaxValue, false))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant, _new.EquipSlot, _new.Entry) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant.Id, _new.EquipSlot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new) .Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -399,7 +399,7 @@ public partial class ModEditWindow
} }
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##imcVariant", SmallIdWidth, _new.Variant, out var variant, 0, byte.MaxValue, false)) if (IdInput("##imcVariant", SmallIdWidth, _new.Variant.Id, out var variant, 0, byte.MaxValue, false))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, variant, _new.EquipSlot, _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, variant, _new.EquipSlot,
_new.Entry).Copy(GetDefault(metaFileManager, _new) _new.Entry).Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -408,7 +408,7 @@ public partial class ModEditWindow
if (_new.ObjectType is ObjectType.DemiHuman) if (_new.ObjectType is ObjectType.DemiHuman)
{ {
if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot)) if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot))
_new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, _new.Entry)
.Copy(GetDefault(metaFileManager, _new) .Copy(GetDefault(metaFileManager, _new)
?? new ImcEntry()); ?? new ImcEntry());
@ -557,7 +557,7 @@ public partial class ModEditWindow
// Identifier // Identifier
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##estId", IdWidth, _new.SetId, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) if (IdInput("##estId", IdWidth, _new.SetId.Id, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
{ {
var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId); var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId);
_new = new EstManipulation(_new.Gender, _new.Race, _new.Slot, setId, newDefaultEntry); _new = new EstManipulation(_new.Gender, _new.Race, _new.Slot, setId, newDefaultEntry);
@ -656,7 +656,7 @@ public partial class ModEditWindow
// Identifier // Identifier
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IdInput("##gmpId", IdWidth, _new.SetId, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) if (IdInput("##gmpId", IdWidth, _new.SetId.Id, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1))
_new = new GmpManipulation(ExpandedGmpFile.GetDefault(metaFileManager, setId), setId); _new = new GmpManipulation(ExpandedGmpFile.GetDefault(metaFileManager, setId), setId);
ImGuiUtil.HoverTooltip(ModelSetIdTooltip); ImGuiUtil.HoverTooltip(ModelSetIdTooltip);

View file

@ -52,7 +52,7 @@ public class ChangedItemDrawer : IDisposable
private readonly ExcelSheet<Item> _items; private readonly ExcelSheet<Item> _items;
private readonly CommunicatorService _communicator; private readonly CommunicatorService _communicator;
private readonly Dictionary<ChangedItemIcon, TextureWrap> _icons = new(16); private readonly Dictionary<ChangedItemIcon, TextureWrap> _icons = new(16);
private float _smallestIconWidth = 0; private float _smallestIconWidth;
public ChangedItemDrawer(UiBuilder uiBuilder, DataManager gameData, CommunicatorService communicator, Configuration config) public ChangedItemDrawer(UiBuilder uiBuilder, DataManager gameData, CommunicatorService communicator, Configuration config)
{ {
@ -265,7 +265,7 @@ public class ChangedItemDrawer : IDisposable
switch (obj) switch (obj)
{ {
case EquipItem it: case EquipItem it:
text = it.WeaponType == 0 ? $"({it.ModelId.Value}-{it.Variant})" : $"({it.ModelId.Value}-{it.WeaponType.Value}-{it.Variant})"; text = it.ModelString;
return true; return true;
case ModelChara m: case ModelChara m:
text = $"({((CharacterBase.ModelType)m.Type).ToName()} {m.Model}-{m.Base}-{m.Variant})"; text = $"({((CharacterBase.ModelType)m.Type).ToName()} {m.Model}-{m.Base}-{m.Variant})";
@ -280,7 +280,7 @@ public class ChangedItemDrawer : IDisposable
private object? Convert(object? data) private object? Convert(object? data)
{ {
if (data is EquipItem it) if (data is EquipItem it)
return _items.GetRow(it.ItemId); return _items.GetRow(it.ItemId.Id);
return data; return data;
} }

View file

@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Group; using FFXIVClientStructs.FFXIV.Client.Game.Group;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -503,10 +504,10 @@ public class DebugTab : Window, ITab
if (table) if (table)
for (var i = 0; i < 8; ++i) for (var i = 0; i < 8; ++i)
{ {
var c = agent->Character(i); ref var c = ref agent->Data->CharacterArraySpan[i];
ImGuiUtil.DrawTableColumn($"Character {i}"); ImGuiUtil.DrawTableColumn($"Character {i}");
var name = c->Name1.ToString(); var name = c.Name1.ToString();
ImGuiUtil.DrawTableColumn(name.Length == 0 ? "NULL" : $"{name} ({c->WorldId})"); ImGuiUtil.DrawTableColumn(name.Length == 0 ? "NULL" : $"{name} ({c.WorldId})");
} }
} }
else else