mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Update for GameData changes.
This commit is contained in:
parent
6dc5916f2b
commit
5d28904bdf
24 changed files with 160 additions and 123 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit c53e578e750d26f83a4e81aca1681c5b01d25a5a
|
||||
Subproject commit bee73fbe1e263d81067029ad97c8e4a263c88147
|
||||
|
|
@ -45,7 +45,7 @@ public readonly struct EqdpCache : IDisposable
|
|||
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
|
||||
{
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
return file != null
|
||||
? file[genderRace, setId.Id]
|
||||
: EstFile.GetDefault(manager, type, genderRace, setId);
|
||||
? file[genderRace, primaryId.Id]
|
||||
: EstFile.GetDefault(manager, type, genderRace, primaryId);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
|
|
|
|||
|
|
@ -187,17 +187,17 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
|||
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? 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);
|
||||
if (eqdpFile != null)
|
||||
return setId.Id < eqdpFile.Count ? eqdpFile[setId] : default;
|
||||
return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default;
|
||||
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)
|
||||
=> _estCache.GetEstEntry(_manager, type, genderRace, setId);
|
||||
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
|
||||
=> _estCache.GetEstEntry(_manager, type, genderRace, primaryId);
|
||||
|
||||
/// <summary> Use this when CharacterUtility becomes ready. </summary>
|
||||
private void ApplyStoredManipulations()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ internal partial record ResolveContext
|
|||
private unsafe GenderRace ResolveModelRaceCode()
|
||||
=> ResolveEqdpRaceCode(Slot, Equipment.Set);
|
||||
|
||||
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, SetId setId)
|
||||
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
|
||||
{
|
||||
var slotIndex = slot.ToIndex();
|
||||
if (slotIndex >= 10 || ModelType != ModelType.Human)
|
||||
|
|
@ -55,7 +55,7 @@ internal partial record ResolveContext
|
|||
if (metaCache == null)
|
||||
return GenderRace.MidlanderMale;
|
||||
|
||||
var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, setId);
|
||||
var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, primaryId);
|
||||
if (entry.ToBits(slot).Item2)
|
||||
return characterRaceCode;
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ internal partial record ResolveContext
|
|||
if (fallbackRaceCode == GenderRace.MidlanderMale)
|
||||
return GenderRace.MidlanderMale;
|
||||
|
||||
entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, setId);
|
||||
entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, primaryId);
|
||||
if (entry.ToBits(slot).Item2)
|
||||
return fallbackRaceCode;
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ internal partial record ResolveContext
|
|||
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 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 equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ internal record GlobalResolveContext(ObjectIdentification Identifier, ModCollect
|
|||
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
||||
|
||||
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
|
||||
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, WeaponType weaponType = default)
|
||||
=> new(this, characterBase, slotIndex, slot, equipment, weaponType);
|
||||
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default)
|
||||
=> new(this, characterBase, slotIndex, slot, equipment, secondaryId);
|
||||
}
|
||||
|
||||
internal partial record ResolveContext(
|
||||
|
|
@ -35,7 +35,7 @@ internal partial record ResolveContext(
|
|||
uint SlotIndex,
|
||||
EquipSlot Slot,
|
||||
CharacterArmor Equipment,
|
||||
WeaponType WeaponType)
|
||||
SecondaryId SecondaryId)
|
||||
{
|
||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
|||
public int Count
|
||||
=> (Length - DataOffset) / EqdpEntrySize;
|
||||
|
||||
public EqdpEntry this[SetId id]
|
||||
public EqdpEntry this[PrimaryId id]
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -79,7 +79,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
|||
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)
|
||||
this[entry] = GetDefault(entry);
|
||||
|
|
@ -101,18 +101,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
|||
Reset();
|
||||
}
|
||||
|
||||
public EqdpEntry GetDefault(SetId setId)
|
||||
=> GetDefault(Manager, Index, setId);
|
||||
public EqdpEntry GetDefault(PrimaryId primaryId)
|
||||
=> GetDefault(Manager, Index, primaryId);
|
||||
|
||||
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, SetId setId)
|
||||
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setId);
|
||||
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, PrimaryId primaryId)
|
||||
=> 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 totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
|
||||
|
||||
var blockIdx = setId.Id / blockSize;
|
||||
var blockIdx = primaryId.Id / blockSize;
|
||||
if (blockIdx >= totalBlockCount)
|
||||
return 0;
|
||||
|
||||
|
|
@ -121,9 +121,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
|||
return 0;
|
||||
|
||||
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)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setId);
|
||||
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
public ulong ControlBlock
|
||||
=> *(ulong*)Data;
|
||||
|
||||
protected ulong GetInternal(SetId idx)
|
||||
protected ulong GetInternal(PrimaryId idx)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -81,13 +81,13 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
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;
|
||||
if (setId == 0)
|
||||
setId = 1;
|
||||
if (primaryId == 0)
|
||||
primaryId = 1;
|
||||
|
||||
var blockIdx = setId.Id / BlockSize;
|
||||
var blockIdx = primaryId.Id / BlockSize;
|
||||
if (blockIdx >= NumBlocks)
|
||||
return def;
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
return def;
|
||||
|
||||
var count = BitOperations.PopCount(control & (blockBit - 1));
|
||||
var idx = setId.Id % BlockSize;
|
||||
var idx = primaryId.Id % BlockSize;
|
||||
var ptr = (ulong*)data + BlockSize * count + idx;
|
||||
return *ptr;
|
||||
}
|
||||
|
|
@ -112,15 +112,15 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
|||
: base(manager, false)
|
||||
{ }
|
||||
|
||||
public EqpEntry this[SetId idx]
|
||||
public EqpEntry this[PrimaryId idx]
|
||||
{
|
||||
get => (EqpEntry)GetInternal(idx);
|
||||
set => SetInternal(idx, (ulong)value);
|
||||
}
|
||||
|
||||
|
||||
public static EqpEntry GetDefault(MetaFileManager manager, SetId setIdx)
|
||||
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry);
|
||||
public static EqpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
|
||||
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, primaryIdx, (ulong)Eqp.DefaultEntry);
|
||||
|
||||
protected override unsafe void SetEmptyBlock(int idx)
|
||||
{
|
||||
|
|
@ -130,7 +130,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
|||
*ptr = (ulong)Eqp.DefaultEntry;
|
||||
}
|
||||
|
||||
public void Reset(IEnumerable<SetId> entries)
|
||||
public void Reset(IEnumerable<PrimaryId> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
this[entry] = GetDefault(Manager, entry);
|
||||
|
|
@ -155,16 +155,16 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
|
|||
: base(manager, true)
|
||||
{ }
|
||||
|
||||
public GmpEntry this[SetId idx]
|
||||
public GmpEntry this[PrimaryId idx]
|
||||
{
|
||||
get => (GmpEntry)GetInternal(idx);
|
||||
set => SetInternal(idx, (ulong)value);
|
||||
}
|
||||
|
||||
public static GmpEntry GetDefault(MetaFileManager manager, SetId setIdx)
|
||||
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default);
|
||||
public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
|
||||
=> (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)
|
||||
this[entry] = GetDefault(Manager, entry);
|
||||
|
|
|
|||
|
|
@ -167,21 +167,21 @@ public sealed unsafe class EstFile : MetaBaseFile
|
|||
public ushort GetDefault(GenderRace genderRace, ushort 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 count = *(int*)data;
|
||||
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)
|
||||
return 0;
|
||||
|
||||
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
||||
}
|
||||
|
||||
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, SetId setId)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
|
||||
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, primaryId);
|
||||
|
||||
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, SetId setId)
|
||||
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
|
||||
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, PrimaryId primaryId)
|
||||
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
|
|||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ModelRace Race { get; private init; }
|
||||
|
||||
public SetId SetId { get; private init; }
|
||||
public PrimaryId SetId { get; private init; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EquipSlot Slot { get; private init; }
|
||||
|
||||
[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;
|
||||
Race = race;
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
|
|||
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
||||
public EqpEntry Entry { get; private init; }
|
||||
|
||||
public SetId SetId { get; private init; }
|
||||
public PrimaryId SetId { get; private init; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EquipSlot Slot { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
public EqpManipulation(EqpEntry entry, EquipSlot slot, SetId setId)
|
||||
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
|
||||
{
|
||||
Slot = slot;
|
||||
SetId = setId;
|
||||
|
|
|
|||
|
|
@ -36,13 +36,14 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
|||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ModelRace Race { get; private init; }
|
||||
|
||||
public SetId SetId { get; private init; }
|
||||
public PrimaryId SetId { get; private init; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public EstType Slot { get; private init; }
|
||||
|
||||
|
||||
[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;
|
||||
Gender = gender;
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ namespace Penumbra.Meta.Manipulations;
|
|||
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
||||
{
|
||||
public GmpEntry Entry { get; private init; }
|
||||
public SetId SetId { get; private init; }
|
||||
public PrimaryId SetId { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
public GmpManipulation(GmpEntry entry, SetId setId)
|
||||
public GmpManipulation(GmpEntry entry, PrimaryId setId)
|
||||
{
|
||||
Entry = entry;
|
||||
SetId = setId;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ namespace Penumbra.Meta.Manipulations;
|
|||
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||
{
|
||||
public ImcEntry Entry { get; private init; }
|
||||
public SetId PrimaryId { get; private init; }
|
||||
public SetId SecondaryId { get; private init; }
|
||||
public PrimaryId PrimaryId { get; private init; }
|
||||
public PrimaryId SecondaryId { get; private init; }
|
||||
public Variant Variant { get; private init; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
|
|
@ -26,7 +26,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
|||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
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;
|
||||
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,
|
||||
// and clamp the variant to 255.
|
||||
[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)
|
||||
{
|
||||
Entry = entry;
|
||||
|
|
|
|||
|
|
@ -270,4 +270,22 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
|
|||
Type.Rsp => $"{Rsp.Entry}",
|
||||
_ => 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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)
|
||||
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,
|
||||
SetId idFrom, SetId idTo, byte variant,
|
||||
PrimaryId idFrom, PrimaryId idTo, byte variant,
|
||||
ref string fileName, ref bool dataWasChanged)
|
||||
{
|
||||
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,
|
||||
SetId idFrom, ref MtrlFile.Texture texture,
|
||||
PrimaryId idFrom, ref MtrlFile.Texture texture,
|
||||
ref bool dataWasChanged)
|
||||
{
|
||||
var path = texture.Path;
|
||||
|
|
|
|||
|
|
@ -185,13 +185,13 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo)
|
||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom,
|
||||
PrimaryId idTo, byte mtrlTo)
|
||||
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
|
||||
|
||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo)
|
||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom,
|
||||
PrimaryId idTo, byte mtrlTo)
|
||||
{
|
||||
var (gender, race) = gr.Split();
|
||||
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,
|
||||
SetId idFrom, SetId idTo, byte mtrlTo)
|
||||
PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
|
||||
=> CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo);
|
||||
|
||||
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()
|
||||
? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom)
|
||||
|
|
@ -236,7 +236,7 @@ public static class EquipmentSwap
|
|||
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();
|
||||
if (!slot.IsEquipmentPiece())
|
||||
|
|
@ -247,7 +247,7 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
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 imc = new ImcFile(manager, entry);
|
||||
|
|
@ -270,8 +270,8 @@ public static class EquipmentSwap
|
|||
return (imc, variants, items);
|
||||
}
|
||||
|
||||
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo)
|
||||
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
|
||||
PrimaryId idTo)
|
||||
{
|
||||
if (slot is not EquipSlot.Head)
|
||||
return null;
|
||||
|
|
@ -283,12 +283,12 @@ public static class EquipmentSwap
|
|||
|
||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
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);
|
||||
|
||||
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, PrimaryId idFrom, PrimaryId idTo,
|
||||
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||
{
|
||||
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
||||
|
|
@ -322,7 +322,7 @@ public static class EquipmentSwap
|
|||
|
||||
|
||||
// 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)
|
||||
return null;
|
||||
|
|
@ -340,8 +340,8 @@ public static class EquipmentSwap
|
|||
return avfx;
|
||||
}
|
||||
|
||||
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo)
|
||||
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
|
||||
PrimaryId idTo)
|
||||
{
|
||||
if (slot.IsAccessory())
|
||||
return null;
|
||||
|
|
@ -353,13 +353,13 @@ public static class EquipmentSwap
|
|||
return new MetaSwap(manips, eqpFrom, eqpTo);
|
||||
}
|
||||
|
||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo, byte variantTo, ref string fileName,
|
||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom,
|
||||
PrimaryId idTo, byte variantTo, ref string fileName,
|
||||
ref bool 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,
|
||||
SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
||||
PrimaryId idFrom, PrimaryId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged)
|
||||
{
|
||||
var prefix = slotTo.IsAccessory() ? 'a' : 'e';
|
||||
|
|
@ -397,13 +397,13 @@ public static class EquipmentSwap
|
|||
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)
|
||||
=> 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,
|
||||
EquipSlot slotTo, SetId idFrom,
|
||||
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||
EquipSlot slotTo, PrimaryId idFrom,
|
||||
PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||
{
|
||||
var path = texture.Path;
|
||||
var addedDashes = false;
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public static class ItemSwap
|
|||
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
||||
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
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)
|
||||
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
|
||||
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
|
||||
: path;
|
||||
|
|
@ -203,10 +203,10 @@ public static class ItemSwap
|
|||
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
|
||||
=> 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);
|
||||
|
||||
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
|
||||
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
|
||||
: path;
|
||||
|
|
@ -219,7 +219,7 @@ public static class ItemSwap
|
|||
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
|
||||
=> 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);
|
||||
|
||||
public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public class ItemSwapContainer
|
|||
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)
|
||||
{
|
||||
var pathResolver = PathResolver(collection);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ public class ModCacheManager : IDisposable
|
|||
{
|
||||
case ModPathChangeType.Added:
|
||||
case ModPathChangeType.Reloaded:
|
||||
Refresh(mod);
|
||||
RefreshWithChangedItems(mod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -151,10 +151,28 @@ public class ModCacheManager : IDisposable
|
|||
}
|
||||
|
||||
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()
|
||||
=> Parallel.ForEach(_modManager, UpdateChangedItems);
|
||||
{
|
||||
if (_updatingItems)
|
||||
return;
|
||||
|
||||
_updatingItems = true;
|
||||
Parallel.ForEach(_modManager, UpdateChangedItems);
|
||||
_updatingItems = false;
|
||||
}
|
||||
|
||||
private static void UpdateFileCount(Mod mod)
|
||||
=> mod.TotalFileCount = mod.AllSubMods.Sum(s => s.Files.Count);
|
||||
|
|
@ -173,15 +191,8 @@ public class ModCacheManager : IDisposable
|
|||
|
||||
private void UpdateChangedItems(Mod mod)
|
||||
{
|
||||
if (_updatingItems)
|
||||
return;
|
||||
|
||||
_updatingItems = true;
|
||||
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
||||
changedItems.Clear();
|
||||
if (!_identifier.Awaiter.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
||||
_identifier.Identify(changedItems, gamePath.ToString());
|
||||
|
||||
|
|
@ -189,7 +200,6 @@ public class ModCacheManager : IDisposable
|
|||
ComputeChangedItems(_identifier, changedItems, manip);
|
||||
|
||||
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
||||
_updatingItems = false;
|
||||
}
|
||||
|
||||
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);
|
||||
UpdateCounts(mod);
|
||||
UpdateChangedItems(mod);
|
||||
}
|
||||
|
||||
private void RefreshWithoutChangedItems(Mod mod)
|
||||
{
|
||||
UpdateTags(mod);
|
||||
UpdateCounts(mod);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ public sealed class SubMod : ISubMod
|
|||
public bool IsDefault
|
||||
=> GroupIdx < 0;
|
||||
|
||||
public Dictionary<Utf8GamePath, FullPath> FileData = new();
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = new();
|
||||
public HashSet<MetaManipulation> ManipulationData = new();
|
||||
public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||
public HashSet<MetaManipulation> ManipulationData = [];
|
||||
|
||||
public SubMod(IMod parentMod)
|
||||
=> ParentMod = parentMod;
|
||||
|
|
|
|||
|
|
@ -91,8 +91,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
|
||||
</Exec>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -210,26 +210,26 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
break;
|
||||
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
(PrimaryId)_sourceId,
|
||||
(PrimaryId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
(PrimaryId)_sourceId,
|
||||
(PrimaryId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Ear, Names.CombinedRace(_currentGender, ModelRace.Viera),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
(PrimaryId)_sourceId,
|
||||
(PrimaryId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace),
|
||||
(SetId)_sourceId,
|
||||
(SetId)_targetId,
|
||||
(PrimaryId)_sourceId,
|
||||
(PrimaryId)_targetId,
|
||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||
break;
|
||||
case SwapType.Weapon: break;
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
|||
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
|
||||
{
|
||||
_modManager.SetKnown(leaf.Value);
|
||||
var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index];
|
||||
if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue