Remove TryGetValue(MetaManipulation) from MetaDictionary.

This commit is contained in:
Ottermandias 2024-06-08 19:09:46 +02:00
parent 5ca9e63a2a
commit 0445ed0ef9
11 changed files with 182 additions and 183 deletions

View file

@ -2,6 +2,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; 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.String.Functions; using Penumbra.String.Functions;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
@ -126,4 +127,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId) public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId); => GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId);
public static EqdpEntry GetDefault(MetaFileManager manager, EqdpIdentifier identifier)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)identifier.FileIndex()], identifier.SetId);
} }

View file

@ -1,6 +1,7 @@
using Penumbra.GameData.Structs; 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.String.Functions; using Penumbra.String.Functions;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
@ -164,6 +165,9 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx) public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
=> new() { Value = GetDefaultInternal(manager, InternalIndex, primaryIdx, GmpEntry.Default.Value) }; => new() { Value = GetDefaultInternal(manager, InternalIndex, primaryIdx, GmpEntry.Default.Value) };
public static GmpEntry GetDefault(MetaFileManager manager, GmpIdentifier identifier)
=> new() { Value = GetDefaultInternal(manager, InternalIndex, identifier.SetId, GmpEntry.Default.Value) };
public void Reset(IEnumerable<PrimaryId> entries) public void Reset(IEnumerable<PrimaryId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)

View file

@ -184,4 +184,7 @@ public sealed unsafe class EstFile : MetaBaseFile
public static EstEntry GetDefault(MetaFileManager manager, EstType estType, GenderRace genderRace, PrimaryId primaryId) public static EstEntry GetDefault(MetaFileManager manager, EstType estType, GenderRace genderRace, PrimaryId primaryId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId); => GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
public static EstEntry GetDefault(MetaFileManager manager, EstIdentifier identifier)
=> GetDefault(manager, identifier.FileIndex(), identifier.GenderRace, identifier.SetId);
} }

View file

@ -65,6 +65,9 @@ public unsafe class ImcFile : MetaBaseFile
return ptr == null ? new ImcEntry() : *ptr; return ptr == null ? new ImcEntry() : *ptr;
} }
public ImcEntry GetEntry(EquipSlot slot, Variant variantIdx)
=> GetEntry(PartIndex(slot), variantIdx);
public ImcEntry GetEntry(int partIdx, Variant variantIdx, out bool exists) public ImcEntry GetEntry(int partIdx, Variant variantIdx, out bool exists)
{ {
var ptr = VariantPtr(Data, partIdx, variantIdx); var ptr = VariantPtr(Data, partIdx, variantIdx);

View file

@ -71,7 +71,7 @@ public readonly record struct EqdpIdentifier(PrimaryId SetId, EquipSlot Slot, Ge
} }
} }
public readonly record struct EqdpEntryInternal(bool Model, bool Material) public readonly record struct EqdpEntryInternal(bool Material, bool Model)
{ {
private EqdpEntryInternal((bool, bool) val) private EqdpEntryInternal((bool, bool) val)
: this(val.Item1, val.Item2) : this(val.Item1, val.Item2)
@ -81,7 +81,6 @@ public readonly record struct EqdpEntryInternal(bool Model, bool Material)
: this(entry.ToBits(slot)) : this(entry.ToBits(slot))
{ } { }
public EqdpEntry ToEntry(EquipSlot slot) public EqdpEntry ToEntry(EquipSlot slot)
=> Eqdp.FromSlotAndBits(slot, Model, Material); => Eqdp.FromSlotAndBits(slot, Material, Model);
} }

View file

@ -52,6 +52,19 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator(); => GetEnumerator();
public bool Add(IMetaIdentifier identifier, object entry)
=> identifier switch
{
EqdpIdentifier eqdpIdentifier => entry is EqdpEntryInternal e && Add(eqdpIdentifier, e),
EqpIdentifier eqpIdentifier => entry is EqpEntryInternal e && Add(eqpIdentifier, e),
EstIdentifier estIdentifier => entry is EstEntry e && Add(estIdentifier, e),
GlobalEqpManipulation globalEqpManipulation => Add(globalEqpManipulation),
GmpIdentifier gmpIdentifier => entry is GmpEntry e && Add(gmpIdentifier, e),
ImcIdentifier imcIdentifier => entry is ImcEntry e && Add(imcIdentifier, e),
RspIdentifier rspIdentifier => entry is RspEntry e && Add(rspIdentifier, e),
_ => false,
};
public bool Add(MetaManipulation manip) public bool Add(MetaManipulation manip)
{ {
var ret = manip.ManipulationType switch var ret = manip.ManipulationType switch
@ -146,71 +159,23 @@ public sealed class MetaDictionary : IEnumerable<MetaManipulation>
TryAdd(identifier); TryAdd(identifier);
} }
public bool TryGetValue(MetaManipulation identifier, out MetaManipulation oldValue) public bool TryGetValue(EstIdentifier identifier, out EstEntry value)
{ => _est.TryGetValue(identifier, out value);
switch (identifier.ManipulationType)
{
case MetaManipulation.Type.Imc:
if (_imc.TryGetValue(identifier.Imc.Identifier, out var oldImc))
{
oldValue = new MetaManipulation(new ImcManipulation(identifier.Imc.Identifier, oldImc));
return true;
}
break; public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value)
case MetaManipulation.Type.Eqdp: => _eqp.TryGetValue(identifier, out value);
if (_eqp.TryGetValue(identifier.Eqp.Identifier, out var oldEqdp))
{
oldValue = new MetaManipulation(new EqpManipulation(identifier.Eqp.Identifier, oldEqdp.ToEntry(identifier.Eqp.Slot)));
return true;
}
break; public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value)
case MetaManipulation.Type.Eqp: => _eqdp.TryGetValue(identifier, out value);
if (_eqdp.TryGetValue(identifier.Eqdp.Identifier, out var oldEqp))
{
oldValue = new MetaManipulation(new EqdpManipulation(identifier.Eqdp.Identifier, oldEqp.ToEntry(identifier.Eqdp.Slot)));
return true;
}
break; public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value)
case MetaManipulation.Type.Est: => _gmp.TryGetValue(identifier, out value);
if (_est.TryGetValue(identifier.Est.Identifier, out var oldEst))
{
oldValue = new MetaManipulation(new EstManipulation(identifier.Est.Identifier, oldEst));
return true;
}
break; public bool TryGetValue(RspIdentifier identifier, out RspEntry value)
case MetaManipulation.Type.Gmp: => _rsp.TryGetValue(identifier, out value);
if (_gmp.TryGetValue(identifier.Gmp.Identifier, out var oldGmp))
{
oldValue = new MetaManipulation(new GmpManipulation(identifier.Gmp.Identifier, oldGmp));
return true;
}
break; public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value)
case MetaManipulation.Type.Rsp: => _imc.TryGetValue(identifier, out value);
if (_rsp.TryGetValue(identifier.Rsp.Identifier, out var oldRsp))
{
oldValue = new MetaManipulation(new RspManipulation(identifier.Rsp.Identifier, oldRsp));
return true;
}
break;
case MetaManipulation.Type.GlobalEqp:
if (_globalEqp.TryGetValue(identifier.GlobalEqp, out var oldGlobalEqp))
{
oldValue = new MetaManipulation(oldGlobalEqp);
return true;
}
break;
}
oldValue = default;
return false;
}
public void SetTo(MetaDictionary other) public void SetTo(MetaDictionary other)
{ {

View file

@ -1,4 +1,4 @@
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
@ -16,32 +16,19 @@ public static class EquipmentSwap
private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger) private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger)
{ {
if (slot != EquipSlot.RFinger) if (slot != EquipSlot.RFinger)
return new[] return [slot];
{
slot,
};
return rFinger return rFinger
? lFinger ? lFinger
? new[] ? [EquipSlot.RFinger, EquipSlot.LFinger]
{ : [EquipSlot.RFinger]
EquipSlot.RFinger,
EquipSlot.LFinger,
}
: new[]
{
EquipSlot.RFinger,
}
: lFinger : lFinger
? new[] ? [EquipSlot.LFinger]
{ : [];
EquipSlot.LFinger,
}
: Array.Empty<EquipSlot>();
} }
public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps, public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips,
EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo) EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo)
{ {
LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom); LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom);
@ -50,11 +37,14 @@ 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.Id, idTo.Id, default); var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo);
var imcFileTo = new ImcFile(manager, imcManip.Identifier); var imcFileTo = new ImcFile(manager, imcIdentifierTo);
var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry)
? entry
: imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant);
var mtrlVariantTo = imcEntry.MaterialId;
var skipFemale = false; var skipFemale = false;
var skipMale = false; var skipMale = false;
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)
@ -99,7 +89,7 @@ public static class EquipmentSwap
} }
public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps, public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipItem itemFrom, Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips, EquipItem itemFrom,
EquipItem itemTo, bool rFinger = true, bool lFinger = true) EquipItem itemTo, bool rFinger = true, bool lFinger = true)
{ {
// Check actual ids, variants and slots. We only support using the same slot. // Check actual ids, variants and slots. We only support using the same slot.
@ -120,8 +110,12 @@ 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.Id, idTo, default); var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo);
var imcFileTo = new ImcFile(manager, imcManip.Identifier); var imcFileTo = new ImcFile(manager, imcIdentifierTo);
var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry)
? entry
: imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant);
var mtrlVariantTo = imcEntry.MaterialId;
var isAccessory = slot.IsAccessory(); var isAccessory = slot.IsAccessory();
var estType = slot switch var estType = slot switch
@ -133,7 +127,6 @@ public static class EquipmentSwap
var skipFemale = false; var skipFemale = false;
var skipMale = false; var skipMale = false;
var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slot), variantTo))).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)
@ -154,7 +147,7 @@ public static class EquipmentSwap
if (eqdp != null) if (eqdp != null)
swaps.Add(eqdp); swaps.Add(eqdp);
var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits(slot).Item2 ?? false; var ownMdl = eqdp?.SwapToModdedEntry.Model ?? false;
var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl); var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl);
if (est != null) if (est != null)
swaps.Add(est); swaps.Add(est);
@ -184,22 +177,22 @@ public static class EquipmentSwap
return affectedItems; return affectedItems;
} }
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap<EqdpIdentifier, EqdpEntryInternal>? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, MetaDictionary manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
PrimaryId idTo, byte mtrlTo)
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo); => CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap<EqdpIdentifier, EqdpEntryInternal>? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom, MetaDictionary manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom,
PrimaryId idTo, byte mtrlTo) PrimaryId idTo, byte mtrlTo)
{ {
var (gender, race) = gr.Split(); var eqdpFromIdentifier = new EqdpIdentifier(idFrom, slotFrom, gr);
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender, var eqdpToIdentifier = new EqdpIdentifier(idFrom, slotFrom, gr);
race, idFrom); var eqdpFromDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpFromIdentifier), slotFrom);
var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo), slotTo, gender, race, var eqdpToDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpToIdentifier), slotTo);
idTo); var meta = new MetaSwap<EqdpIdentifier, EqdpEntryInternal>(i => manips.TryGetValue(i, out var e) ? e : null, eqdpFromIdentifier,
var meta = new MetaSwap(manips, eqdpFrom, eqdpTo); eqdpFromDefault, eqdpToIdentifier,
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom); eqdpToDefault);
var (ownMtrl, ownMdl) = meta.SwapToModdedEntry;
if (ownMdl) if (ownMdl)
{ {
var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo); var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo);
@ -270,38 +263,39 @@ public static class EquipmentSwap
return (imc, variants, items); return (imc, variants, items);
} }
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom, public static MetaSwap<GmpIdentifier, GmpEntry>? CreateGmp(MetaFileManager manager, MetaDictionary manips,
PrimaryId idTo) EquipSlot slot, PrimaryId idFrom, PrimaryId idTo)
{ {
if (slot is not EquipSlot.Head) if (slot is not EquipSlot.Head)
return null; return null;
var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom), idFrom); var manipFromIdentifier = new GmpIdentifier(idFrom);
var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo), idTo); var manipToIdentifier = new GmpIdentifier(idTo);
return new MetaSwap(manips, manipFrom, manipTo); var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier);
var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier);
return new MetaSwap<GmpIdentifier, GmpEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
} }
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap<ImcIdentifier, ImcEntry> CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, MetaDictionary manips, EquipSlot slot, PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo,
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) 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, public static MetaSwap<ImcIdentifier, ImcEntry> CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, MetaDictionary manips, EquipSlot slotFrom, EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo,
EquipSlot slotFrom, EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo,
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
{ {
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); var manipFromIdentifier = new ImcIdentifier(slotFrom, idFrom, variantFrom);
var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo);
var manipulationFrom = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, entryFrom); var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
var manipulationTo = new ImcManipulation(slotTo, variantTo.Id, idTo, entryTo); var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo);
var imc = new MetaSwap(manips, manipulationFrom, manipulationTo); var imc = new MetaSwap<ImcIdentifier, ImcEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId); var decal = CreateDecal(manager, redirections, imc.SwapToModdedEntry.DecalId);
if (decal != null) if (decal != null)
imc.ChildSwaps.Add(decal); imc.ChildSwaps.Add(decal);
var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId); var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModdedEntry.VfxId);
if (avfx != null) if (avfx != null)
imc.ChildSwaps.Add(avfx); imc.ChildSwaps.Add(avfx);
@ -322,7 +316,8 @@ public static class EquipmentSwap
// Example: Abyssos Helm / Body // Example: Abyssos Helm / Body
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, PrimaryId idFrom, PrimaryId idTo, byte vfxId) public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, PrimaryId idFrom, PrimaryId idTo,
byte vfxId)
{ {
if (vfxId == 0) if (vfxId == 0)
return null; return null;
@ -340,17 +335,18 @@ public static class EquipmentSwap
return avfx; return avfx;
} }
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom, public static MetaSwap<EqpIdentifier, EqpEntryInternal>? CreateEqp(MetaFileManager manager, MetaDictionary manips,
PrimaryId idTo) EquipSlot slot, PrimaryId idFrom, PrimaryId idTo)
{ {
if (slot.IsAccessory()) if (slot.IsAccessory())
return null; return null;
var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom); var manipFromIdentifier = new EqpIdentifier(idFrom, slot);
var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo); var manipToIdentifier = new EqpIdentifier(idTo, slot);
var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom); var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot);
var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom); var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot);
return new MetaSwap(manips, eqpFrom, eqpTo); return new MetaSwap<EqpIdentifier, EqpEntryInternal>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier,
manipFromDefault, manipToIdentifier, manipToDefault);
} }
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom, public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom,
@ -397,7 +393,8 @@ public static class EquipmentSwap
return mtrl; return mtrl;
} }
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, PrimaryId idFrom, PrimaryId idTo, public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, PrimaryId idFrom,
PrimaryId idTo,
ref MtrlFile.Texture texture, ref bool dataWasChanged) ref MtrlFile.Texture texture, ref bool dataWasChanged)
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged); => CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);

View file

@ -147,24 +147,23 @@ public static class ItemSwap
return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath); return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath);
} }
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks> public static MetaSwap<EstIdentifier, EstEntry>? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, MetaDictionary manips, EstType type, GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
Func<MetaManipulation, MetaManipulation> manips, EstType type,
GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
{ {
if (type == 0) if (type == 0)
return null; return null;
var (gender, race) = genderRace.Split(); var manipFromIdentifier = new EstIdentifier(idFrom, type, genderRace);
var fromDefault = new EstManipulation(gender, race, type, idFrom, EstFile.GetDefault(manager, type, genderRace, idFrom)); var manipToIdentifier = new EstIdentifier(idTo, type, genderRace);
var toDefault = new EstManipulation(gender, race, type, idTo, EstFile.GetDefault(manager, type, genderRace, idTo)); var manipFromDefault = EstFile.GetDefault(manager, manipFromIdentifier);
var est = new MetaSwap(manips, fromDefault, toDefault); var manipToDefault = EstFile.GetDefault(manager, manipToIdentifier);
var est = new MetaSwap<EstIdentifier, EstEntry>(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault);
if (ownMdl && est.SwapApplied.Est.Entry.Value >= 2) if (ownMdl && est.SwapToModdedEntry.Value >= 2)
{ {
var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry); var phyb = CreatePhyb(manager, redirections, type, genderRace, est.SwapToModdedEntry);
est.ChildSwaps.Add(phyb); est.ChildSwaps.Add(phyb);
var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapApplied.Est.Entry); var sklb = CreateSklb(manager, redirections, type, genderRace, est.SwapToModdedEntry);
est.ChildSwaps.Add(sklb); est.ChildSwaps.Add(sklb);
} }
else if (est.SwapAppliedIsDefault) else if (est.SwapAppliedIsDefault)

View file

@ -74,9 +74,9 @@ public class ItemSwapContainer
} }
break; break;
case MetaSwap meta: case IMetaSwap meta:
if (!meta.SwapAppliedIsDefault) if (!meta.SwapAppliedIsDefault)
convertedManips.Add(meta.SwapApplied); convertedManips.Add(meta.SwapFromIdentifier, meta.SwapToModdedEntry);
break; break;
} }
@ -116,17 +116,10 @@ public class ItemSwapContainer
? p => collection.ResolvePath(p) ?? new FullPath(p) ? p => collection.ResolvePath(p) ?? new FullPath(p)
: p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p); : p => ModRedirections.TryGetValue(p, out var path) ? path : new FullPath(p);
private Func<MetaManipulation, MetaManipulation> MetaResolver(ModCollection? collection) private MetaDictionary MetaResolver(ModCollection? collection)
{ => collection?.MetaCache?.Manipulations is { } cache
if (collection?.MetaCache?.Manipulations is { } cache) ? [.. cache]
{ : _appliedModData.Manipulations;
MetaDictionary dict = [.. cache];
return m => dict.TryGetValue(m, out var a) ? a : m;
}
var set = _appliedModData.Manipulations;
return m => set.TryGetValue(m, out var a) ? a : m;
}
public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true,
bool useLeftRing = true) bool useLeftRing = true)
@ -161,8 +154,8 @@ public class ItemSwapContainer
_ => (EstType)0, _ => (EstType)0,
}; };
var metaResolver = MetaResolver(collection); var estResolver = MetaResolver(collection);
var est = ItemSwap.CreateEst(manager, pathResolver, metaResolver, type, race, from, to, true); var est = ItemSwap.CreateEst(manager, pathResolver, estResolver, type, race, from, to, true);
Swaps.Add(mdl); Swaps.Add(mdl);
if (est != null) if (est != null)

View file

@ -1,58 +1,91 @@
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Meta; using Penumbra.Meta;
using static Penumbra.Mods.ItemSwap.ItemSwap; using static Penumbra.Mods.ItemSwap.ItemSwap;
using Penumbra.Services;
namespace Penumbra.Mods.ItemSwap; namespace Penumbra.Mods.ItemSwap;
public class Swap public class Swap
{ {
/// <summary> Any further swaps belonging specifically to this tree of changes. </summary> /// <summary> Any further swaps belonging specifically to this tree of changes. </summary>
public readonly List<Swap> ChildSwaps = new(); public readonly List<Swap> ChildSwaps = [];
public IEnumerable<Swap> WithChildren() public IEnumerable<Swap> WithChildren()
=> ChildSwaps.SelectMany(c => c.WithChildren()).Prepend(this); => ChildSwaps.SelectMany(c => c.WithChildren()).Prepend(this);
} }
public sealed class MetaSwap : Swap public interface IMetaSwap
{ {
public IMetaIdentifier SwapFromIdentifier { get; }
public IMetaIdentifier SwapToIdentifier { get; }
public object SwapFromDefaultEntry { get; }
public object SwapToDefaultEntry { get; }
public object SwapToModdedEntry { get; }
public bool SwapToIsDefault { get; }
public bool SwapAppliedIsDefault { get; }
}
public sealed class MetaSwap<TIdentifier, TEntry> : Swap, IMetaSwap
where TIdentifier : unmanaged, IMetaIdentifier
where TEntry : unmanaged, IEquatable<TEntry>
{
public TIdentifier SwapFromIdentifier;
public TIdentifier SwapToIdentifier;
/// <summary> The default value of a specific meta manipulation that needs to be redirected. </summary> /// <summary> The default value of a specific meta manipulation that needs to be redirected. </summary>
public MetaManipulation SwapFrom; public TEntry SwapFromDefaultEntry;
/// <summary> The default value of the same Meta entry of the redirected item. </summary> /// <summary> The default value of the same Meta entry of the redirected item. </summary>
public MetaManipulation SwapToDefault; public TEntry SwapToDefaultEntry;
/// <summary> The modded value of the same Meta entry of the redirected item, or the same as SwapToDefault if unmodded. </summary> /// <summary> The modded value of the same Meta entry of the redirected item, or the same as SwapToDefault if unmodded. </summary>
public MetaManipulation SwapToModded; public TEntry SwapToModdedEntry;
/// <summary> The modded value applied to the specific meta manipulation target before redirection. </summary> /// <summary> Whether SwapToModdedEntry equals SwapToDefaultEntry. </summary>
public MetaManipulation SwapApplied; public bool SwapToIsDefault { get; }
/// <summary> Whether SwapToModded equals SwapToDefault. </summary>
public bool SwapToIsDefault;
/// <summary> Whether the applied meta manipulation does not change anything against the default. </summary> /// <summary> Whether the applied meta manipulation does not change anything against the default. </summary>
public bool SwapAppliedIsDefault; public bool SwapAppliedIsDefault { get; }
/// <summary> /// <summary>
/// Create a new MetaSwap from the original meta identifier and the target meta identifier. /// Create a new MetaSwap from the original meta identifier and the target meta identifier.
/// </summary> /// </summary>
/// <param name="manipulations">A function that converts the given manipulation to the modded one.</param> /// <param name="manipulations">A function that obtains a modded meta entry if it exists. </param>
/// <param name="manipFrom">The original meta identifier with its default value.</param> /// <param name="manipFromIdentifier"> The original meta identifier. </param>
/// <param name="manipTo">The target meta identifier with its default value.</param> /// <param name="manipFromEntry"> The default value for the original meta identifier. </param>
public MetaSwap(Func<MetaManipulation, MetaManipulation> manipulations, MetaManipulation manipFrom, MetaManipulation manipTo) /// <param name="manipToIdentifier"> The target meta identifier. </param>
/// <param name="manipToEntry"> The default value for the target meta identifier. </param>
public MetaSwap(Func<TIdentifier, TEntry?> manipulations, TIdentifier manipFromIdentifier, TEntry manipFromEntry,
TIdentifier manipToIdentifier, TEntry manipToEntry)
{ {
SwapFrom = manipFrom; SwapFromIdentifier = manipFromIdentifier;
SwapToDefault = manipTo; SwapToIdentifier = manipToIdentifier;
SwapFromDefaultEntry = manipFromEntry;
SwapToDefaultEntry = manipToEntry;
SwapToModded = manipulations(manipTo); SwapToModdedEntry = manipulations(SwapToIdentifier) ?? SwapToDefaultEntry;
SwapToIsDefault = manipTo.EntryEquals(SwapToModded); SwapToIsDefault = SwapToModdedEntry.Equals(SwapToDefaultEntry);
SwapApplied = SwapFrom.WithEntryOf(SwapToModded); SwapAppliedIsDefault = SwapToModdedEntry.Equals(SwapFromDefaultEntry);
SwapAppliedIsDefault = SwapApplied.EntryEquals(SwapFrom);
} }
IMetaIdentifier IMetaSwap.SwapFromIdentifier
=> SwapFromIdentifier;
IMetaIdentifier IMetaSwap.SwapToIdentifier
=> SwapToIdentifier;
object IMetaSwap.SwapFromDefaultEntry
=> SwapFromDefaultEntry;
object IMetaSwap.SwapToDefaultEntry
=> SwapToDefaultEntry;
object IMetaSwap.SwapToModdedEntry
=> SwapToModdedEntry;
} }
public sealed class FileSwap : Swap public sealed class FileSwap : Swap
@ -113,8 +146,7 @@ public sealed class FileSwap : Swap
/// <param name="swap">A full swap container with the actual file in memory.</param> /// <param name="swap">A full swap container with the actual file in memory.</param>
/// <returns>True if everything could be read correctly, false otherwise.</returns> /// <returns>True if everything could be read correctly, false otherwise.</returns>
public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func<Utf8GamePath, FullPath> redirections, public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func<Utf8GamePath, FullPath> redirections,
string swapFromRequest, string swapToRequest, string swapFromRequest, string swapToRequest, string? swapFromPreChange = null)
string? swapFromPreChange = null)
{ {
var swap = new FileSwap var swap = new FileSwap
{ {

View file

@ -240,7 +240,7 @@ public class ItemSwapTab : IDisposable, ITab
{ {
return swap switch return swap switch
{ {
MetaSwap meta => $"{meta.SwapFrom}: {meta.SwapFrom.EntryToString()} -> {meta.SwapApplied.EntryToString()}", IMetaSwap meta => $"{meta.SwapFromIdentifier}: {meta.SwapFromDefaultEntry} -> {meta.SwapToModdedEntry}",
FileSwap file => FileSwap file =>
$"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{(file.DataWasChanged ? " (EDITED)" : string.Empty)}", $"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{(file.DataWasChanged ? " (EDITED)" : string.Empty)}",
_ => string.Empty, _ => string.Empty,
@ -410,7 +410,7 @@ public class ItemSwapTab : IDisposable, ITab
private ImRaii.IEndObject DrawTab(SwapType newTab) private ImRaii.IEndObject DrawTab(SwapType newTab)
{ {
using var tab = ImRaii.TabItem(newTab is SwapType.BetweenSlots ? "Between Slots" : newTab.ToString()); var tab = ImRaii.TabItem(newTab is SwapType.BetweenSlots ? "Between Slots" : newTab.ToString());
if (tab) if (tab)
{ {
_dirty |= _lastTab != newTab; _dirty |= _lastTab != newTab;