Update for GameData changes.

This commit is contained in:
Ottermandias 2023-12-20 16:39:26 +01:00
parent 6dc5916f2b
commit 5d28904bdf
24 changed files with 160 additions and 123 deletions

@ -1 +1 @@
Subproject commit c53e578e750d26f83a4e81aca1681c5b01d25a5a Subproject commit bee73fbe1e263d81067029ad97c8e4a263c88147

View file

@ -45,7 +45,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 => (SetId)m.SetId)); file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (PrimaryId)m.SetId));
} }
_eqdpManipulations.Clear(); _eqdpManipulations.Clear();

View file

@ -74,12 +74,12 @@ public struct EstCache : IDisposable
}; };
} }
internal ushort GetEstEntry(MetaFileManager manager, EstManipulation.EstType type, GenderRace genderRace, SetId setId) internal ushort GetEstEntry(MetaFileManager manager, EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
{ {
var file = GetEstFile(type); var file = GetEstFile(type);
return file != null return file != null
? file[genderRace, setId.Id] ? file[genderRace, primaryId.Id]
: EstFile.GetDefault(manager, type, genderRace, setId); : EstFile.GetDefault(manager, type, genderRace, primaryId);
} }
public void Reset() public void Reset()

View file

@ -187,17 +187,17 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
=> _imcCache.GetImcFile(path, out file); => _imcCache.GetImcFile(path, out file);
internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, SetId setId) internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId)
{ {
var eqdpFile = _eqdpCache.EqdpFile(race, accessory); var eqdpFile = _eqdpCache.EqdpFile(race, accessory);
if (eqdpFile != null) if (eqdpFile != null)
return setId.Id < eqdpFile.Count ? eqdpFile[setId] : default; return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default;
else else
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, setId); return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
} }
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, SetId setId) internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
=> _estCache.GetEstEntry(_manager, type, genderRace, setId); => _estCache.GetEstEntry(_manager, type, genderRace, primaryId);
/// <summary> Use this when CharacterUtility becomes ready. </summary> /// <summary> Use this when CharacterUtility becomes ready. </summary>
private void ApplyStoredManipulations() private void ApplyStoredManipulations()

View file

@ -37,7 +37,7 @@ internal partial record ResolveContext
private unsafe GenderRace ResolveModelRaceCode() private unsafe GenderRace ResolveModelRaceCode()
=> ResolveEqdpRaceCode(Slot, Equipment.Set); => ResolveEqdpRaceCode(Slot, Equipment.Set);
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, SetId setId) private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
{ {
var slotIndex = slot.ToIndex(); var slotIndex = slot.ToIndex();
if (slotIndex >= 10 || ModelType != ModelType.Human) if (slotIndex >= 10 || ModelType != ModelType.Human)
@ -55,7 +55,7 @@ internal partial record ResolveContext
if (metaCache == null) if (metaCache == null)
return GenderRace.MidlanderMale; return GenderRace.MidlanderMale;
var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, setId); var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, primaryId);
if (entry.ToBits(slot).Item2) if (entry.ToBits(slot).Item2)
return characterRaceCode; return characterRaceCode;
@ -63,7 +63,7 @@ internal partial record ResolveContext
if (fallbackRaceCode == GenderRace.MidlanderMale) if (fallbackRaceCode == GenderRace.MidlanderMale)
return GenderRace.MidlanderMale; return GenderRace.MidlanderMale;
entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, setId); entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, primaryId);
if (entry.ToBits(slot).Item2) if (entry.ToBits(slot).Item2)
return fallbackRaceCode; return fallbackRaceCode;
@ -229,7 +229,7 @@ internal partial record ResolveContext
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
} }
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanSkeletonData(uint partialSkeletonIndex) private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanSkeletonData(uint partialSkeletonIndex)
{ {
var human = (Human*)CharacterBase.Value; var human = (Human*)CharacterBase.Value;
var characterRaceCode = (GenderRace)human->RaceSexId; var characterRaceCode = (GenderRace)human->RaceSexId;
@ -262,17 +262,17 @@ internal partial record ResolveContext
} }
} }
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstManipulation.EstType type) private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstManipulation.EstType type)
{ {
var human = (Human*)CharacterBase.Value; var human = (Human*)CharacterBase.Value;
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()]; var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set); return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
} }
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstManipulation.EstType type, SetId set) private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstManipulation.EstType type, PrimaryId primary)
{ {
var metaCache = Global.Collection.MetaCache; var metaCache = Global.Collection.MetaCache;
var skeletonSet = metaCache == null ? default : metaCache.GetEstEntry(type, raceCode, set); var skeletonSet = metaCache == null ? default : metaCache.GetEstEntry(type, raceCode, primary);
return (raceCode, EstManipulation.ToName(type), skeletonSet); return (raceCode, EstManipulation.ToName(type), skeletonSet);
} }

View file

@ -25,8 +25,8 @@ internal record GlobalResolveContext(ObjectIdentification Identifier, ModCollect
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128); public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu, public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, WeaponType weaponType = default) EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default)
=> new(this, characterBase, slotIndex, slot, equipment, weaponType); => new(this, characterBase, slotIndex, slot, equipment, secondaryId);
} }
internal partial record ResolveContext( internal partial record ResolveContext(
@ -35,7 +35,7 @@ internal partial record ResolveContext(
uint SlotIndex, uint SlotIndex,
EquipSlot Slot, EquipSlot Slot,
CharacterArmor Equipment, CharacterArmor Equipment,
WeaponType WeaponType) SecondaryId SecondaryId)
{ {
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true); private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);

View file

@ -38,7 +38,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public int Count public int Count
=> (Length - DataOffset) / EqdpEntrySize; => (Length - DataOffset) / EqdpEntrySize;
public EqdpEntry this[SetId id] public EqdpEntry this[PrimaryId id]
{ {
get get
{ {
@ -79,7 +79,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<SetId> entries) public void Reset(IEnumerable<PrimaryId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(entry); this[entry] = GetDefault(entry);
@ -101,18 +101,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
Reset(); Reset();
} }
public EqdpEntry GetDefault(SetId setId) public EqdpEntry GetDefault(PrimaryId primaryId)
=> GetDefault(Manager, Index, setId); => GetDefault(Manager, Index, primaryId);
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, SetId setId) public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, PrimaryId primaryId)
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setId); => GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, primaryId);
public static EqdpEntry GetDefault(byte* data, SetId setId) public static EqdpEntry GetDefault(byte* data, PrimaryId primaryId)
{ {
var blockSize = *(ushort*)(data + IdentifierSize); var blockSize = *(ushort*)(data + IdentifierSize);
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2); var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
var blockIdx = setId.Id / blockSize; var blockIdx = primaryId.Id / blockSize;
if (blockIdx >= totalBlockCount) if (blockIdx >= totalBlockCount)
return 0; return 0;
@ -121,9 +121,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 + setId.Id % blockSize)); return (EqdpEntry)(*(blockData + primaryId.Id % blockSize));
} }
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, SetId setId) public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setId); => GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId);
} }

View file

@ -24,7 +24,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public ulong ControlBlock public ulong ControlBlock
=> *(ulong*)Data; => *(ulong*)Data;
protected ulong GetInternal(SetId idx) protected ulong GetInternal(PrimaryId idx)
{ {
return idx.Id switch return idx.Id switch
{ {
@ -34,7 +34,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
}; };
} }
protected void SetInternal(SetId idx, ulong value) protected void SetInternal(PrimaryId idx, ulong value)
{ {
idx = idx.Id switch idx = idx.Id switch
{ {
@ -81,13 +81,13 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
Reset(); Reset();
} }
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, SetId setId, ulong def) protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, PrimaryId primaryId, ulong def)
{ {
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address; var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
if (setId == 0) if (primaryId == 0)
setId = 1; primaryId = 1;
var blockIdx = setId.Id / BlockSize; var blockIdx = primaryId.Id / BlockSize;
if (blockIdx >= NumBlocks) if (blockIdx >= NumBlocks)
return def; return def;
@ -97,7 +97,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 = setId.Id % BlockSize; var idx = primaryId.Id % BlockSize;
var ptr = (ulong*)data + BlockSize * count + idx; var ptr = (ulong*)data + BlockSize * count + idx;
return *ptr; return *ptr;
} }
@ -112,15 +112,15 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
: base(manager, false) : base(manager, false)
{ } { }
public EqpEntry this[SetId idx] public EqpEntry this[PrimaryId 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, SetId setIdx) public static EqpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry); => (EqpEntry)GetDefaultInternal(manager, InternalIndex, primaryIdx, (ulong)Eqp.DefaultEntry);
protected override unsafe void SetEmptyBlock(int idx) protected override unsafe void SetEmptyBlock(int idx)
{ {
@ -130,7 +130,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
*ptr = (ulong)Eqp.DefaultEntry; *ptr = (ulong)Eqp.DefaultEntry;
} }
public void Reset(IEnumerable<SetId> entries) public void Reset(IEnumerable<PrimaryId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry); this[entry] = GetDefault(Manager, entry);
@ -155,16 +155,16 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
: base(manager, true) : base(manager, true)
{ } { }
public GmpEntry this[SetId idx] public GmpEntry this[PrimaryId 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, SetId setIdx) public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default); => (GmpEntry)GetDefaultInternal(manager, InternalIndex, primaryIdx, (ulong)GmpEntry.Default);
public void Reset(IEnumerable<SetId> entries) public void Reset(IEnumerable<PrimaryId> entries)
{ {
foreach (var entry in entries) foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry); this[entry] = GetDefault(Manager, entry);

View file

@ -167,21 +167,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, SetId setId) public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, PrimaryId primaryId)
{ {
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.Id); var (idx, found) = FindEntry(span, genderRace, primaryId.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, SetId setId) public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId); => GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, primaryId);
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, SetId setId) public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, PrimaryId primaryId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId); => GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
} }

View file

@ -18,13 +18,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 SetId SetId { get; private init; } public PrimaryId 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, SetId setId) public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
{ {
Gender = gender; Gender = gender;
Race = race; Race = race;

View file

@ -15,13 +15,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 SetId SetId { get; private init; } public PrimaryId 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, SetId setId) public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
{ {
Slot = slot; Slot = slot;
SetId = setId; SetId = setId;

View file

@ -36,13 +36,14 @@ 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 SetId SetId { get; private init; } public PrimaryId 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, SetId setId, ushort entry) public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, ushort entry)
{ {
Entry = entry; Entry = entry;
Gender = gender; Gender = gender;

View file

@ -8,11 +8,11 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
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 SetId SetId { get; private init; } public PrimaryId SetId { get; private init; }
[JsonConstructor] [JsonConstructor]
public GmpManipulation(GmpEntry entry, SetId setId) public GmpManipulation(GmpEntry entry, PrimaryId setId)
{ {
Entry = entry; Entry = entry;
SetId = setId; SetId = setId;

View file

@ -12,10 +12,10 @@ 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 SetId PrimaryId { get; private init; } public PrimaryId PrimaryId { get; private init; }
public SetId SecondaryId { get; private init; } public PrimaryId SecondaryId { get; private init; }
public Variant 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; }
@ -26,7 +26,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public BodySlot BodySlot { get; private init; } public BodySlot BodySlot { get; private init; }
public ImcManipulation(EquipSlot equipSlot, ushort variant, SetId primaryId, ImcEntry entry) public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry)
{ {
Entry = entry; Entry = entry;
PrimaryId = primaryId; PrimaryId = primaryId;
@ -42,7 +42,7 @@ 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, SetId primaryId, SetId secondaryId, ushort variant, internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, PrimaryId secondaryId, ushort variant,
EquipSlot equipSlot, ImcEntry entry) EquipSlot equipSlot, ImcEntry entry)
{ {
Entry = entry; Entry = entry;

View file

@ -269,5 +269,23 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
Type.Gmp => $"{Gmp.Entry.Value}", Type.Gmp => $"{Gmp.Entry.Value}",
Type.Rsp => $"{Rsp.Entry}", Type.Rsp => $"{Rsp.Entry}",
_ => string.Empty, _ => string.Empty,
}; };
public static bool operator ==(MetaManipulation left, MetaManipulation right)
=> left.Equals(right);
public static bool operator !=(MetaManipulation left, MetaManipulation right)
=> !(left == right);
public static bool operator <(MetaManipulation left, MetaManipulation right)
=> left.CompareTo(right) < 0;
public static bool operator <=(MetaManipulation left, MetaManipulation right)
=> left.CompareTo(right) <= 0;
public static bool operator >(MetaManipulation left, MetaManipulation right)
=> left.CompareTo(right) > 0;
public static bool operator >=(MetaManipulation left, MetaManipulation right)
=> left.CompareTo(right) >= 0;
} }

View file

@ -12,7 +12,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, public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, SetId idTo) PrimaryId idFrom, PrimaryId idTo)
{ {
if (idFrom.Id > 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}.");
@ -43,7 +43,7 @@ public static class CustomizationSwap
} }
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race, public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, SetId idTo, byte variant, PrimaryId idFrom, PrimaryId idTo, byte variant,
ref string fileName, ref bool dataWasChanged) ref string fileName, ref bool dataWasChanged)
{ {
variant = slot is BodySlot.Face or BodySlot.Ear ? byte.MaxValue : variant; variant = slot is BodySlot.Face or BodySlot.Ear ? byte.MaxValue : variant;
@ -79,7 +79,7 @@ public static class CustomizationSwap
} }
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race, public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
SetId idFrom, ref MtrlFile.Texture texture, PrimaryId idFrom, ref MtrlFile.Texture texture,
ref bool dataWasChanged) ref bool dataWasChanged)
{ {
var path = texture.Path; var path = texture.Path;

View file

@ -185,13 +185,13 @@ public static class EquipmentSwap
} }
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, SetId idFrom, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom,
SetId 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? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom,
SetId idTo, byte mtrlTo) PrimaryId idTo, byte mtrlTo)
{ {
var (gender, race) = gr.Split(); var (gender, race) = gr.Split();
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender, var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender,
@ -214,11 +214,11 @@ public static class EquipmentSwap
} }
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, GenderRace gr, public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, GenderRace gr,
SetId idFrom, SetId idTo, byte mtrlTo) PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
=> CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo); => CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo);
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo, public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo) GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
{ {
var mdlPathFrom = slotFrom.IsAccessory() var mdlPathFrom = slotFrom.IsAccessory()
? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom) ? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom)
@ -236,7 +236,7 @@ public static class EquipmentSwap
return mdl; return mdl;
} }
private static void LookupItem(EquipItem i, out EquipSlot slot, out SetId modelId, out Variant variant) private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId modelId, out Variant variant)
{ {
slot = i.Type.ToSlot(); slot = i.Type.ToSlot();
if (!slot.IsEquipmentPiece()) if (!slot.IsEquipmentPiece())
@ -247,7 +247,7 @@ public static class EquipmentSwap
} }
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom, private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom,
SetId idFrom, SetId idTo, Variant variantFrom) PrimaryId idFrom, PrimaryId idTo, Variant variantFrom)
{ {
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default); var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry); var imc = new ImcFile(manager, entry);
@ -270,8 +270,8 @@ public static class EquipmentSwap
return (imc, variants, items); return (imc, variants, items);
} }
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom, public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
SetId idTo) PrimaryId idTo)
{ {
if (slot is not EquipSlot.Head) if (slot is not EquipSlot.Head)
return null; return null;
@ -283,12 +283,12 @@ public static class EquipmentSwap
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant 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, public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, Func<MetaManipulation, MetaManipulation> manips,
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId 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 entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
@ -322,7 +322,7 @@ public static class EquipmentSwap
// Example: Abyssos Helm / Body // Example: Abyssos Helm / Body
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, SetId idFrom, SetId 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,8 +340,8 @@ public static class EquipmentSwap
return avfx; return avfx;
} }
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom, public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
SetId idTo) PrimaryId idTo)
{ {
if (slot.IsAccessory()) if (slot.IsAccessory())
return null; return null;
@ -353,13 +353,13 @@ public static class EquipmentSwap
return new MetaSwap(manips, eqpFrom, eqpTo); return new MetaSwap(manips, eqpFrom, eqpTo);
} }
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, SetId idFrom, public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom,
SetId idTo, byte variantTo, ref string fileName, PrimaryId idTo, byte variantTo, ref string fileName,
ref bool dataWasChanged) ref bool dataWasChanged)
=> CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged); => CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged);
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo, public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
SetId idFrom, SetId idTo, byte variantTo, ref string fileName, PrimaryId idFrom, PrimaryId idTo, byte variantTo, ref string fileName,
ref bool dataWasChanged) ref bool dataWasChanged)
{ {
var prefix = slotTo.IsAccessory() ? 'a' : 'e'; var prefix = slotTo.IsAccessory() ? 'a' : 'e';
@ -397,13 +397,13 @@ public static class EquipmentSwap
return mtrl; return mtrl;
} }
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, SetId idFrom, SetId 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);
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom, public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
EquipSlot slotTo, SetId idFrom, EquipSlot slotTo, PrimaryId idFrom,
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
{ {
var path = texture.Path; var path = texture.Path;
var addedDashes = false; var addedDashes = false;

View file

@ -150,7 +150,7 @@ public static class ItemSwap
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks> /// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type, Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl) GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
{ {
if (type == 0) if (type == 0)
return null; return null;
@ -195,7 +195,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, PrimaryId id, bool condition = true)
=> condition => condition
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}") ? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
: path; : path;
@ -203,10 +203,10 @@ public static class ItemSwap
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true) public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
=> ReplaceAnyId(path, 'c', (ushort)to, condition); => ReplaceAnyId(path, 'c', (ushort)to, condition);
public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true) public static string ReplaceAnyBody(string path, BodySlot slot, PrimaryId to, bool condition = true)
=> ReplaceAnyId(path, slot.ToAbbreviation(), to, condition); => ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
public static string ReplaceId(string path, char type, SetId idFrom, SetId idTo, bool condition = true) public static string ReplaceId(string path, char type, PrimaryId idFrom, PrimaryId idTo, bool condition = true)
=> condition => condition
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}") ? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
: path; : path;
@ -219,7 +219,7 @@ public static class ItemSwap
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true) public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition); => ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true) public static string ReplaceBody(string path, BodySlot slot, PrimaryId idFrom, PrimaryId idTo, bool condition = true)
=> ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition); => ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
public static string AddSuffix(string path, string ext, string suffix, bool condition = true) public static string AddSuffix(string path, string ext, string suffix, bool condition = true)

View file

@ -145,7 +145,7 @@ public class ItemSwapContainer
return ret; return ret;
} }
public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to, public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, PrimaryId from, PrimaryId to,
ModCollection? collection = null) ModCollection? collection = null)
{ {
var pathResolver = PathResolver(collection); var pathResolver = PathResolver(collection);

View file

@ -139,7 +139,7 @@ public class ModCacheManager : IDisposable
{ {
case ModPathChangeType.Added: case ModPathChangeType.Added:
case ModPathChangeType.Reloaded: case ModPathChangeType.Reloaded:
Refresh(mod); RefreshWithChangedItems(mod);
break; break;
} }
} }
@ -151,10 +151,28 @@ public class ModCacheManager : IDisposable
} }
private void OnModDiscoveryFinished() private void OnModDiscoveryFinished()
=> Parallel.ForEach(_modManager, Refresh); {
if (!_identifier.Awaiter.IsCompletedSuccessfully || _updatingItems)
{
Parallel.ForEach(_modManager, RefreshWithoutChangedItems);
}
else
{
_updatingItems = true;
Parallel.ForEach(_modManager, RefreshWithChangedItems);
_updatingItems = false;
}
}
private void OnIdentifierCreation() private void OnIdentifierCreation()
=> Parallel.ForEach(_modManager, UpdateChangedItems); {
if (_updatingItems)
return;
_updatingItems = true;
Parallel.ForEach(_modManager, UpdateChangedItems);
_updatingItems = false;
}
private static void UpdateFileCount(Mod mod) private static void UpdateFileCount(Mod mod)
=> mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count); => mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count);
@ -173,15 +191,8 @@ public class ModCacheManager : IDisposable
private void UpdateChangedItems(Mod mod) private void UpdateChangedItems(Mod mod)
{ {
if (_updatingItems)
return;
_updatingItems = true;
var changedItems = (SortedList<string, object?>)mod.ChangedItems; var changedItems = (SortedList<string, object?>)mod.ChangedItems;
changedItems.Clear(); changedItems.Clear();
if (!_identifier.Awaiter.IsCompletedSuccessfully)
return;
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys))) foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
_identifier.Identify(changedItems, gamePath.ToString()); _identifier.Identify(changedItems, gamePath.ToString());
@ -189,7 +200,6 @@ public class ModCacheManager : IDisposable
ComputeChangedItems(_identifier, changedItems, manip); ComputeChangedItems(_identifier, changedItems, manip);
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant())); mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
_updatingItems = false;
} }
private static void UpdateCounts(Mod mod) private static void UpdateCounts(Mod mod)
@ -210,10 +220,16 @@ public class ModCacheManager : IDisposable
} }
} }
private void Refresh(Mod mod) private void RefreshWithChangedItems(Mod mod)
{ {
UpdateTags(mod); UpdateTags(mod);
UpdateCounts(mod); UpdateCounts(mod);
UpdateChangedItems(mod); UpdateChangedItems(mod);
} }
private void RefreshWithoutChangedItems(Mod mod)
{
UpdateTags(mod);
UpdateCounts(mod);
}
} }

View file

@ -30,9 +30,9 @@ public sealed class SubMod : ISubMod
public bool IsDefault public bool IsDefault
=> GroupIdx < 0; => GroupIdx < 0;
public Dictionary<Utf8GamePath, FullPath> FileData = new(); public Dictionary<Utf8GamePath, FullPath> FileData = [];
public Dictionary<Utf8GamePath, FullPath> FileSwapData = new(); public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
public HashSet<MetaManipulation> ManipulationData = new(); public HashSet<MetaManipulation> ManipulationData = [];
public SubMod(IMod parentMod) public SubMod(IMod parentMod)
=> ParentMod = parentMod; => ParentMod = parentMod;

View file

@ -91,8 +91,9 @@
</ItemGroup> </ItemGroup>
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion"> <Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low"> <Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" /> <Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
</Exec> </Exec>
<PropertyGroup> <PropertyGroup>

View file

@ -210,26 +210,26 @@ public class ItemSwapTab : IDisposable, ITab
break; break;
case SwapType.Hair when _targetId > 0 && _sourceId > 0: case SwapType.Hair when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), _swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace),
(SetId)_sourceId, (PrimaryId)_sourceId,
(SetId)_targetId, (PrimaryId)_targetId,
_useCurrentCollection ? _collectionManager.Active.Current : null); _useCurrentCollection ? _collectionManager.Active.Current : null);
break; break;
case SwapType.Face when _targetId > 0 && _sourceId > 0: case SwapType.Face when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), _swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace),
(SetId)_sourceId, (PrimaryId)_sourceId,
(SetId)_targetId, (PrimaryId)_targetId,
_useCurrentCollection ? _collectionManager.Active.Current : null); _useCurrentCollection ? _collectionManager.Active.Current : null);
break; break;
case SwapType.Ears when _targetId > 0 && _sourceId > 0: case SwapType.Ears when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization(_metaFileManager, BodySlot.Ear, Names.CombinedRace(_currentGender, ModelRace.Viera), _swapData.LoadCustomization(_metaFileManager, BodySlot.Ear, Names.CombinedRace(_currentGender, ModelRace.Viera),
(SetId)_sourceId, (PrimaryId)_sourceId,
(SetId)_targetId, (PrimaryId)_targetId,
_useCurrentCollection ? _collectionManager.Active.Current : null); _useCurrentCollection ? _collectionManager.Active.Current : null);
break; break;
case SwapType.Tail when _targetId > 0 && _sourceId > 0: case SwapType.Tail when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), _swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace),
(SetId)_sourceId, (PrimaryId)_sourceId,
(SetId)_targetId, (PrimaryId)_targetId,
_useCurrentCollection ? _collectionManager.Active.Current : null); _useCurrentCollection ? _collectionManager.Active.Current : null);
break; break;
case SwapType.Weapon: break; case SwapType.Weapon: break;

View file

@ -169,6 +169,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose(); ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle)) if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
{ {
_modManager.SetKnown(leaf.Value);
var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index]; var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index];
if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive()) if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
{ {