mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 13:14:17 +01:00
Make attributes and shapes completely toggleable.
This commit is contained in:
parent
75f4e66dbf
commit
b48c4f440a
6 changed files with 245 additions and 156 deletions
|
|
@ -8,10 +8,12 @@ namespace Penumbra.Collections.Cache;
|
||||||
public sealed class AtrCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtrIdentifier, AtrEntry>(manager, collection)
|
public sealed class AtrCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtrIdentifier, AtrEntry>(manager, collection)
|
||||||
{
|
{
|
||||||
public bool ShouldBeDisabled(in ShapeAttributeString attribute, HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
public bool ShouldBeDisabled(in ShapeAttributeString attribute, HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
||||||
=> DisabledCount > 0 && _atrData.TryGetValue(attribute, out var value) && value.Contains(slot, id, genderRace);
|
=> DisabledCount > 0 && _atrData.TryGetValue(attribute, out var value) && value.CheckEntry(slot, id, genderRace) is false;
|
||||||
|
|
||||||
|
public int EnabledCount { get; private set; }
|
||||||
public int DisabledCount { get; private set; }
|
public int DisabledCount { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> Data
|
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> Data
|
||||||
=> _atrData;
|
=> _atrData;
|
||||||
|
|
||||||
|
|
@ -21,24 +23,28 @@ public sealed class AtrCache(MetaFileManager manager, ModCollection collection)
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
_atrData.Clear();
|
_atrData.Clear();
|
||||||
|
DisabledCount = 0;
|
||||||
|
EnabledCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool _)
|
protected override void Dispose(bool _)
|
||||||
=> Clear();
|
=> Reset();
|
||||||
|
|
||||||
protected override void ApplyModInternal(AtrIdentifier identifier, AtrEntry entry)
|
protected override void ApplyModInternal(AtrIdentifier identifier, AtrEntry entry)
|
||||||
{
|
{
|
||||||
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
|
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
|
||||||
{
|
{
|
||||||
if (entry.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
value = [];
|
value = [];
|
||||||
_atrData.Add(identifier.Attribute, value);
|
_atrData.Add(identifier.Attribute, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, !entry.Value))
|
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value, out _))
|
||||||
++DisabledCount;
|
{
|
||||||
|
if (entry.Value)
|
||||||
|
++EnabledCount;
|
||||||
|
else
|
||||||
|
++DisabledCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void RevertModInternal(AtrIdentifier identifier)
|
protected override void RevertModInternal(AtrIdentifier identifier)
|
||||||
|
|
@ -46,9 +52,12 @@ public sealed class AtrCache(MetaFileManager manager, ModCollection collection)
|
||||||
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
|
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, false))
|
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, null, out var which))
|
||||||
{
|
{
|
||||||
--DisabledCount;
|
if (which)
|
||||||
|
--EnabledCount;
|
||||||
|
else
|
||||||
|
--DisabledCount;
|
||||||
if (value.IsEmpty)
|
if (value.IsEmpty)
|
||||||
_atrData.Remove(identifier.Attribute);
|
_atrData.Remove(identifier.Attribute);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,93 +19,126 @@ public sealed class ShapeAttributeHashSet : Dictionary<(HumanSlot Slot, PrimaryI
|
||||||
public static readonly FrozenDictionary<GenderRace, int> GenderRaceIndices =
|
public static readonly FrozenDictionary<GenderRace, int> GenderRaceIndices =
|
||||||
GenderRaceValues.WithIndex().ToFrozenDictionary(p => p.Value, p => p.Index);
|
GenderRaceValues.WithIndex().ToFrozenDictionary(p => p.Value, p => p.Index);
|
||||||
|
|
||||||
private readonly BitArray _allIds = new((ShapeAttributeManager.ModelSlotSize + 1) * GenderRaceValues.Count);
|
private readonly BitArray _allIds = new(2 * (ShapeAttributeManager.ModelSlotSize + 1) * GenderRaceValues.Count);
|
||||||
|
|
||||||
|
public bool? this[HumanSlot slot]
|
||||||
|
=> AllCheck(ToIndex(slot, 0));
|
||||||
|
|
||||||
|
public bool? this[GenderRace genderRace]
|
||||||
|
=> ToIndex(HumanSlot.Unknown, genderRace, out var index) ? AllCheck(index) : null;
|
||||||
|
|
||||||
|
public bool? this[HumanSlot slot, GenderRace genderRace]
|
||||||
|
=> ToIndex(slot, genderRace, out var index) ? AllCheck(index) : null;
|
||||||
|
|
||||||
|
public bool? All
|
||||||
|
=> Convert(_allIds[2 * AllIndex], _allIds[2 * AllIndex + 1]);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private bool CheckGroups(HumanSlot slot, GenderRace genderRace)
|
private bool? AllCheck(int idx)
|
||||||
{
|
=> Convert(_allIds[idx], _allIds[idx + 1]);
|
||||||
if (All || this[slot])
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_allIds[ToIndex(HumanSlot.Unknown, index)])
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return _allIds[ToIndex(slot, index)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool this[HumanSlot slot]
|
|
||||||
=> _allIds[ToIndex(slot, 0)];
|
|
||||||
|
|
||||||
public bool this[GenderRace genderRace]
|
|
||||||
=> ToIndex(HumanSlot.Unknown, genderRace, out var index) && _allIds[index];
|
|
||||||
|
|
||||||
public bool this[HumanSlot slot, GenderRace genderRace]
|
|
||||||
=> ToIndex(slot, genderRace, out var index) && _allIds[index];
|
|
||||||
|
|
||||||
public bool All
|
|
||||||
=> _allIds[AllIndex];
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private static int ToIndex(HumanSlot slot, int genderRaceIndex)
|
private static int ToIndex(HumanSlot slot, int genderRaceIndex)
|
||||||
=> slot is HumanSlot.Unknown ? genderRaceIndex + AllIndex : genderRaceIndex + (int)slot * GenderRaceValues.Count;
|
=> 2 * (slot is HumanSlot.Unknown ? genderRaceIndex + AllIndex : genderRaceIndex + (int)slot * GenderRaceValues.Count);
|
||||||
|
|
||||||
public bool Contains(HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
=> CheckGroups(slot, genderRace) || ContainsEntry(slot, id, genderRace);
|
public bool? CheckEntry(HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private bool ContainsEntry(HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
|
||||||
=> GenderRaceIndices.TryGetValue(genderRace, out var index)
|
|
||||||
&& TryGetValue((slot, id), out var flags)
|
|
||||||
&& ((flags & 1ul) is not 0 || (flags & (1ul << index)) is not 0);
|
|
||||||
|
|
||||||
public bool TrySet(HumanSlot slot, PrimaryId? id, GenderRace genderRace, bool value)
|
|
||||||
{
|
{
|
||||||
|
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Check for specific ID.
|
||||||
|
if (TryGetValue((slot, id), out var flags))
|
||||||
|
{
|
||||||
|
// Check completely specified entry.
|
||||||
|
if (Convert(flags, 2 * index) is { } specified)
|
||||||
|
return specified;
|
||||||
|
|
||||||
|
// Check any gender / race.
|
||||||
|
if (Convert(flags, 0) is { } anyGr)
|
||||||
|
return anyGr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specified gender / race and slot, but no ID.
|
||||||
|
if (AllCheck(ToIndex(slot, index)) is { } noIdButGr)
|
||||||
|
return noIdButGr;
|
||||||
|
|
||||||
|
// Check for specified gender / race but no slot or ID.
|
||||||
|
if (AllCheck(ToIndex(HumanSlot.Unknown, index)) is { } noSlotButGr)
|
||||||
|
return noSlotButGr;
|
||||||
|
|
||||||
|
// Check for specified slot but no gender / race or ID.
|
||||||
|
if (AllCheck(ToIndex(slot, 0)) is { } noGrButSlot)
|
||||||
|
return noGrButSlot;
|
||||||
|
|
||||||
|
return All;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySet(HumanSlot slot, PrimaryId? id, GenderRace genderRace, bool? value, out bool which)
|
||||||
|
{
|
||||||
|
which = false;
|
||||||
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
|
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!id.HasValue)
|
if (!id.HasValue)
|
||||||
{
|
{
|
||||||
var slotIndex = ToIndex(slot, index);
|
var slotIndex = ToIndex(slot, index);
|
||||||
var old = _allIds[slotIndex];
|
var ret = false;
|
||||||
_allIds[slotIndex] = value;
|
if (value is true)
|
||||||
return old != value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
if (TryGetValue((slot, id.Value), out var flags))
|
|
||||||
{
|
{
|
||||||
var newFlags = flags | (1ul << index);
|
if (!_allIds[slotIndex])
|
||||||
if (newFlags == flags)
|
ret = true;
|
||||||
return false;
|
_allIds[slotIndex] = true;
|
||||||
|
_allIds[slotIndex + 1] = false;
|
||||||
|
}
|
||||||
|
else if (value is false)
|
||||||
|
{
|
||||||
|
if (!_allIds[slotIndex + 1])
|
||||||
|
ret = true;
|
||||||
|
_allIds[slotIndex] = false;
|
||||||
|
_allIds[slotIndex + 1] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_allIds[slotIndex])
|
||||||
|
{
|
||||||
|
which = true;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
else if (_allIds[slotIndex + 1])
|
||||||
|
{
|
||||||
|
which = false;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
this[(slot, id.Value)] = newFlags;
|
_allIds[slotIndex] = false;
|
||||||
return true;
|
_allIds[slotIndex + 1] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this[(slot, id.Value)] = 1ul << index;
|
return ret;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (TryGetValue((slot, id.Value), out var flags))
|
|
||||||
|
if (TryGetValue((slot, id.Value), out var flags))
|
||||||
{
|
{
|
||||||
var newFlags = flags & ~(1ul << index);
|
var newFlags = value switch
|
||||||
|
{
|
||||||
|
true => (flags | (1ul << index)) & ~(1ul << (index + 1)),
|
||||||
|
false => (flags & ~(1ul << index)) | (1ul << (index + 1)),
|
||||||
|
_ => flags & ~(1ul << index) & ~(1ul << (index + 1)),
|
||||||
|
};
|
||||||
if (newFlags == flags)
|
if (newFlags == flags)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (newFlags is 0)
|
|
||||||
{
|
|
||||||
Remove((slot, id.Value));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this[(slot, id.Value)] = newFlags;
|
this[(slot, id.Value)] = newFlags;
|
||||||
|
which = (flags & (1ul << index)) is not 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (value is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this[(slot, id.Value)] = 1ul << (index + (value.Value ? 0 : 1));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public new void Clear()
|
public new void Clear()
|
||||||
|
|
@ -128,4 +161,20 @@ public sealed class ShapeAttributeHashSet : Dictionary<(HumanSlot Slot, PrimaryI
|
||||||
index = ToIndex(slot, index);
|
index = ToIndex(slot, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private static bool? Convert(bool trueValue, bool falseValue)
|
||||||
|
=> trueValue ? true : falseValue ? false : null;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private static bool? Convert(ulong mask, int idx)
|
||||||
|
{
|
||||||
|
mask >>= idx;
|
||||||
|
return (mask & 3) switch
|
||||||
|
{
|
||||||
|
1 => true,
|
||||||
|
2 => false,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ namespace Penumbra.Collections.Cache;
|
||||||
public sealed class ShpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ShpIdentifier, ShpEntry>(manager, collection)
|
public sealed class ShpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ShpIdentifier, ShpEntry>(manager, collection)
|
||||||
{
|
{
|
||||||
public bool ShouldBeEnabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
public bool ShouldBeEnabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
||||||
=> EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.Contains(slot, id, genderRace);
|
=> EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.CheckEntry(slot, id, genderRace) is true;
|
||||||
|
|
||||||
|
public bool ShouldBeDisabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace)
|
||||||
|
=> DisabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.CheckEntry(slot, id, genderRace) is false;
|
||||||
|
|
||||||
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> State(ShapeConnectorCondition connector)
|
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> State(ShapeConnectorCondition connector)
|
||||||
=> connector switch
|
=> connector switch
|
||||||
|
|
@ -20,7 +23,8 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
|
||||||
_ => [],
|
_ => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
public int EnabledCount { get; private set; }
|
public int EnabledCount { get; private set; }
|
||||||
|
public int DisabledCount { get; private set; }
|
||||||
|
|
||||||
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _shpData = [];
|
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _shpData = [];
|
||||||
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _wristConnectors = [];
|
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _wristConnectors = [];
|
||||||
|
|
@ -34,10 +38,12 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
|
||||||
_wristConnectors.Clear();
|
_wristConnectors.Clear();
|
||||||
_waistConnectors.Clear();
|
_waistConnectors.Clear();
|
||||||
_ankleConnectors.Clear();
|
_ankleConnectors.Clear();
|
||||||
|
EnabledCount = 0;
|
||||||
|
DisabledCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool _)
|
protected override void Dispose(bool _)
|
||||||
=> Clear();
|
=> Reset();
|
||||||
|
|
||||||
protected override void ApplyModInternal(ShpIdentifier identifier, ShpEntry entry)
|
protected override void ApplyModInternal(ShpIdentifier identifier, ShpEntry entry)
|
||||||
{
|
{
|
||||||
|
|
@ -55,15 +61,17 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
|
||||||
{
|
{
|
||||||
if (!dict.TryGetValue(identifier.Shape, out var value))
|
if (!dict.TryGetValue(identifier.Shape, out var value))
|
||||||
{
|
{
|
||||||
if (!entry.Value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
value = [];
|
value = [];
|
||||||
dict.Add(identifier.Shape, value);
|
dict.Add(identifier.Shape, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value))
|
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value, out _))
|
||||||
++EnabledCount;
|
{
|
||||||
|
if (entry.Value)
|
||||||
|
++EnabledCount;
|
||||||
|
else
|
||||||
|
++DisabledCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,9 +92,12 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
|
||||||
if (!dict.TryGetValue(identifier.Shape, out var value))
|
if (!dict.TryGetValue(identifier.Shape, out var value))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, false))
|
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, null, out var which))
|
||||||
{
|
{
|
||||||
--EnabledCount;
|
if (which)
|
||||||
|
--EnabledCount;
|
||||||
|
else
|
||||||
|
--DisabledCount;
|
||||||
if (value.IsEmpty)
|
if (value.IsEmpty)
|
||||||
dict.Remove(identifier.Shape);
|
dict.Remove(identifier.Shape);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,46 +106,59 @@ public unsafe class ShapeAttributeManager : IRequiredService, IDisposable
|
||||||
|
|
||||||
private void UpdateDefaultMasks(Model human, ShpCache cache)
|
private void UpdateDefaultMasks(Model human, ShpCache cache)
|
||||||
{
|
{
|
||||||
|
var genderRace = (GenderRace)human.AsHuman->RaceSexId;
|
||||||
foreach (var (shape, topIndex) in _temporaryShapes[1])
|
foreach (var (shape, topIndex) in _temporaryShapes[1])
|
||||||
{
|
{
|
||||||
if (shape.IsWrist() && _temporaryShapes[2].TryGetValue(shape, out var handIndex))
|
if (shape.IsWrist()
|
||||||
|
&& _temporaryShapes[2].TryGetValue(shape, out var handIndex)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Body, _ids[1], genderRace)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Hands, _ids[2], genderRace)
|
||||||
|
&& human.AsHuman->Models[1] is not null
|
||||||
|
&& human.AsHuman->Models[2] is not null)
|
||||||
{
|
{
|
||||||
human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex;
|
human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex;
|
||||||
human.AsHuman->Models[2]->EnabledShapeKeyIndexMask |= 1u << handIndex;
|
human.AsHuman->Models[2]->EnabledShapeKeyIndexMask |= 1u << handIndex;
|
||||||
CheckCondition(cache.State(ShapeConnectorCondition.Wrists), HumanSlot.Body, HumanSlot.Hands, 1, 2);
|
CheckCondition(cache.State(ShapeConnectorCondition.Wrists), genderRace, HumanSlot.Body, HumanSlot.Hands, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shape.IsWaist() && _temporaryShapes[3].TryGetValue(shape, out var legIndex))
|
if (shape.IsWaist()
|
||||||
|
&& _temporaryShapes[3].TryGetValue(shape, out var legIndex)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Body, _ids[1], genderRace)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Legs, _ids[3], genderRace)
|
||||||
|
&& human.AsHuman->Models[1] is not null
|
||||||
|
&& human.AsHuman->Models[3] is not null)
|
||||||
{
|
{
|
||||||
human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex;
|
human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex;
|
||||||
human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << legIndex;
|
human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << legIndex;
|
||||||
CheckCondition(cache.State(ShapeConnectorCondition.Waist), HumanSlot.Body, HumanSlot.Legs, 1, 3);
|
CheckCondition(cache.State(ShapeConnectorCondition.Waist), genderRace, HumanSlot.Body, HumanSlot.Legs, 1, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (shape, bottomIndex) in _temporaryShapes[3])
|
foreach (var (shape, bottomIndex) in _temporaryShapes[3])
|
||||||
{
|
{
|
||||||
if (shape.IsAnkle() && _temporaryShapes[4].TryGetValue(shape, out var footIndex))
|
if (shape.IsAnkle()
|
||||||
|
&& _temporaryShapes[4].TryGetValue(shape, out var footIndex)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Legs, _ids[3], genderRace)
|
||||||
|
&& !cache.ShouldBeDisabled(shape, HumanSlot.Feet, _ids[4], genderRace)
|
||||||
|
&& human.AsHuman->Models[3] is not null
|
||||||
|
&& human.AsHuman->Models[4] is not null)
|
||||||
{
|
{
|
||||||
human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << bottomIndex;
|
human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << bottomIndex;
|
||||||
human.AsHuman->Models[4]->EnabledShapeKeyIndexMask |= 1u << footIndex;
|
human.AsHuman->Models[4]->EnabledShapeKeyIndexMask |= 1u << footIndex;
|
||||||
CheckCondition(cache.State(ShapeConnectorCondition.Ankles), HumanSlot.Legs, HumanSlot.Feet, 3, 4);
|
CheckCondition(cache.State(ShapeConnectorCondition.Ankles), genderRace, HumanSlot.Legs, HumanSlot.Feet, 3, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
void CheckCondition(IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> dict, HumanSlot slot1,
|
void CheckCondition(IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> dict, GenderRace genderRace, HumanSlot slot1,
|
||||||
HumanSlot slot2, int idx1, int idx2)
|
HumanSlot slot2, int idx1, int idx2)
|
||||||
{
|
{
|
||||||
if (dict.Count is 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (shape, set) in dict)
|
foreach (var (shape, set) in dict)
|
||||||
{
|
{
|
||||||
if (set.Contains(slot1, _ids[idx1], GenderRace.Unknown) && _temporaryShapes[idx1].TryGetValue(shape, out var index1))
|
if (set.CheckEntry(slot1, _ids[idx1], genderRace) is true && _temporaryShapes[idx1].TryGetValue(shape, out var index1))
|
||||||
human.AsHuman->Models[idx1]->EnabledShapeKeyIndexMask |= 1u << index1;
|
human.AsHuman->Models[idx1]->EnabledShapeKeyIndexMask |= 1u << index1;
|
||||||
if (set.Contains(slot2, _ids[idx2], GenderRace.Unknown) && _temporaryShapes[idx2].TryGetValue(shape, out var index2))
|
if (set.CheckEntry(slot2, _ids[idx2], genderRace) is true && _temporaryShapes[idx2].TryGetValue(shape, out var index2))
|
||||||
human.AsHuman->Models[idx2]->EnabledShapeKeyIndexMask |= 1u << index2;
|
human.AsHuman->Models[idx2]->EnabledShapeKeyIndexMask |= 1u << index2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ namespace Penumbra.UI;
|
||||||
public sealed class ConfigWindow : Window, IUiService
|
public sealed class ConfigWindow : Window, IUiService
|
||||||
{
|
{
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly PerformanceTracker _tracker;
|
private readonly PerformanceTracker _tracker;
|
||||||
private readonly ValidityChecker _validityChecker;
|
private readonly ValidityChecker _validityChecker;
|
||||||
private Penumbra? _penumbra;
|
private Penumbra? _penumbra;
|
||||||
private ConfigTabBar _configTabs = null!;
|
private ConfigTabBar _configTabs = null!;
|
||||||
private string? _lastException;
|
private string? _lastException;
|
||||||
|
|
||||||
public ConfigWindow(PerformanceTracker tracker, IDalamudPluginInterface pi, Configuration config, ValidityChecker checker,
|
public ConfigWindow(PerformanceTracker tracker, IDalamudPluginInterface pi, Configuration config, ValidityChecker checker,
|
||||||
TutorialService tutorial)
|
TutorialService tutorial)
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,14 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImUtf8.TableSetupColumn("Attribute"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
ImUtf8.TableSetupColumn("Attribute"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
||||||
ImUtf8.TableSetupColumn("Disabled"u8, ImGuiTableColumnFlags.WidthStretch);
|
ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
foreach (var (attribute, set) in data.ModCollection.MetaCache!.Atr.Data)
|
foreach (var (attribute, set) in data.ModCollection.MetaCache!.Atr.Data.OrderBy(a => a.Key))
|
||||||
DrawShapeAttribute(attribute, set);
|
{
|
||||||
|
ImUtf8.DrawTableColumn(attribute.AsSpan);
|
||||||
|
DrawValues(attribute, set);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawCollectionShapeCache(Actor actor)
|
private unsafe void DrawCollectionShapeCache(Actor actor)
|
||||||
|
|
@ -72,83 +75,87 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
|
||||||
|
|
||||||
ImUtf8.TableSetupColumn("Condition"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
ImUtf8.TableSetupColumn("Condition"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
||||||
ImUtf8.TableSetupColumn("Shape"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
ImUtf8.TableSetupColumn("Shape"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale);
|
||||||
ImUtf8.TableSetupColumn("Enabled"u8, ImGuiTableColumnFlags.WidthStretch);
|
ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
foreach (var condition in Enum.GetValues<ShapeConnectorCondition>())
|
foreach (var condition in Enum.GetValues<ShapeConnectorCondition>())
|
||||||
{
|
{
|
||||||
foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State(condition))
|
foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State(condition).OrderBy(shp => shp.Key))
|
||||||
{
|
{
|
||||||
ImUtf8.DrawTableColumn(condition.ToString());
|
ImUtf8.DrawTableColumn(condition.ToString());
|
||||||
DrawShapeAttribute(shape, set);
|
ImUtf8.DrawTableColumn(shape.AsSpan);
|
||||||
|
DrawValues(shape, set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawShapeAttribute(in ShapeAttributeString shapeAttribute, ShapeAttributeHashSet set)
|
private static void DrawValues(in ShapeAttributeString shapeAttribute, ShapeAttributeHashSet set)
|
||||||
{
|
{
|
||||||
ImUtf8.DrawTableColumn(shapeAttribute.AsSpan);
|
ImGui.TableNextColumn();
|
||||||
if (set.All)
|
|
||||||
{
|
|
||||||
ImUtf8.DrawTableColumn("All"u8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
foreach (var slot in ShapeAttributeManager.UsedModels)
|
|
||||||
{
|
|
||||||
if (!set[slot])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ImUtf8.Text($"All {slot.ToName()}, ");
|
if (set.All is { } value)
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value);
|
||||||
|
ImUtf8.Text("All"u8);
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in ShapeAttributeManager.UsedModels)
|
||||||
|
{
|
||||||
|
if (set[slot] is not { } value2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value2);
|
||||||
|
ImUtf8.Text($"All {slot.ToName()}, ");
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1))
|
||||||
|
{
|
||||||
|
if (set[gr] is { } value3)
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value3);
|
||||||
|
ImUtf8.Text($"All {gr.ToName()}, ");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1))
|
|
||||||
{
|
{
|
||||||
if (set[gr])
|
foreach (var slot in ShapeAttributeManager.UsedModels)
|
||||||
{
|
{
|
||||||
ImUtf8.Text($"All {gr.ToName()}, ");
|
if (set[slot, gr] is not { } value4)
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var slot in ShapeAttributeManager.UsedModels)
|
|
||||||
{
|
|
||||||
if (!set[slot, gr])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ImUtf8.Text($"All {gr.ToName()} {slot.ToName()}, ");
|
|
||||||
ImGui.SameLine(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var ((slot, id), flags) in set)
|
|
||||||
{
|
|
||||||
if ((flags & 1ul) is not 0)
|
|
||||||
{
|
|
||||||
if (set[slot])
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ImUtf8.Text($"{slot.ToName()} {id.Id:D4}, ");
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value4);
|
||||||
|
ImUtf8.Text($"All {gr.ToName()} {slot.ToName()}, ");
|
||||||
ImGui.SameLine(0, 0);
|
ImGui.SameLine(0, 0);
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
var currentFlags = flags >> 1;
|
|
||||||
var currentIndex = BitOperations.TrailingZeroCount(currentFlags);
|
|
||||||
while (currentIndex < ShapeAttributeHashSet.GenderRaceValues.Count)
|
|
||||||
{
|
|
||||||
var gr = ShapeAttributeHashSet.GenderRaceValues[currentIndex];
|
|
||||||
if (set[slot, gr])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ImUtf8.Text($"{gr.ToName()} {slot.ToName()} {id.Id:D4}, ");
|
foreach (var ((slot, id), flags) in set)
|
||||||
currentFlags >>= currentIndex;
|
{
|
||||||
currentIndex = BitOperations.TrailingZeroCount(currentFlags);
|
if ((flags & 3) is not 0)
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), (flags & 2) is not 0);
|
||||||
|
ImUtf8.Text($"{slot.ToName()} {id.Id:D4}, ");
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var currentFlags = flags >> 2;
|
||||||
|
var currentIndex = BitOperations.TrailingZeroCount(currentFlags) / 2;
|
||||||
|
while (currentIndex < ShapeAttributeHashSet.GenderRaceValues.Count)
|
||||||
|
{
|
||||||
|
var value5 = (currentFlags & 1) is 1;
|
||||||
|
var gr = ShapeAttributeHashSet.GenderRaceValues[currentIndex];
|
||||||
|
if (set[slot, gr] != value5)
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value5);
|
||||||
|
ImUtf8.Text($"{gr.ToName()} {slot.ToName()} #{id.Id:D4}, ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentFlags >>= currentIndex * 2;
|
||||||
|
currentIndex = BitOperations.TrailingZeroCount(currentFlags) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue