Update shp conditions.

This commit is contained in:
Ottermandias 2025-05-18 15:52:47 +02:00
parent fbc4c2d054
commit e326e3d809
8 changed files with 137 additions and 179 deletions

View file

@ -8,27 +8,24 @@ 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 ShapeString shape, HumanSlot slot, PrimaryId id) public bool ShouldBeEnabled(in ShapeString shape, HumanSlot slot, PrimaryId id)
=> _shpData.TryGetValue(shape, out var value) && value.Contains(slot, id); => EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.Contains(slot, id);
internal IReadOnlyDictionary<ShapeString, ShpHashSet> State internal IReadOnlyDictionary<ShapeString, ShpHashSet> State(ShapeConnectorCondition connector)
=> _shpData; => connector switch
internal IEnumerable<(ShapeString, IReadOnlyDictionary<ShapeString, ShpHashSet>)> ConditionState
=> _conditionalSet.Select(kvp => (kvp.Key, (IReadOnlyDictionary<ShapeString, ShpHashSet>)kvp.Value));
public bool CheckConditionState(ShapeString condition, [NotNullWhen(true)] out IReadOnlyDictionary<ShapeString, ShpHashSet>? dict)
{ {
if (_conditionalSet.TryGetValue(condition, out var d)) ShapeConnectorCondition.None => _shpData,
{ ShapeConnectorCondition.Wrists => _wristConnectors,
dict = d; ShapeConnectorCondition.Waist => _waistConnectors,
return true; ShapeConnectorCondition.Ankles => _ankleConnectors,
} _ => [],
};
dict = null; public int EnabledCount { get; private set; }
return false;
}
public bool ShouldBeEnabled(ShapeConnectorCondition connector, in ShapeString shape, HumanSlot slot, PrimaryId id)
=> State(connector).TryGetValue(shape, out var value) && value.Contains(slot, id);
public sealed class ShpHashSet : HashSet<(HumanSlot Slot, PrimaryId Id)> public sealed class ShpHashSet : HashSet<(HumanSlot Slot, PrimaryId Id)>
{ {
private readonly BitArray _allIds = new(ShapeManager.ModelSlotSize); private readonly BitArray _allIds = new(ShapeManager.ModelSlotSize);
@ -93,13 +90,17 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
} }
private readonly Dictionary<ShapeString, ShpHashSet> _shpData = []; private readonly Dictionary<ShapeString, ShpHashSet> _shpData = [];
private readonly Dictionary<ShapeString, Dictionary<ShapeString, ShpHashSet>> _conditionalSet = []; private readonly Dictionary<ShapeString, ShpHashSet> _wristConnectors = [];
private readonly Dictionary<ShapeString, ShpHashSet> _waistConnectors = [];
private readonly Dictionary<ShapeString, ShpHashSet> _ankleConnectors = [];
public void Reset() public void Reset()
{ {
Clear(); Clear();
_shpData.Clear(); _shpData.Clear();
_conditionalSet.Clear(); _wristConnectors.Clear();
_waistConnectors.Clear();
_ankleConnectors.Clear();
} }
protected override void Dispose(bool _) protected override void Dispose(bool _)
@ -107,24 +108,16 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
protected override void ApplyModInternal(ShpIdentifier identifier, ShpEntry entry) protected override void ApplyModInternal(ShpIdentifier identifier, ShpEntry entry)
{ {
if (identifier.ShapeCondition.Length > 0) switch (identifier.ConnectorCondition)
{ {
if (!_conditionalSet.TryGetValue(identifier.ShapeCondition, out var shapes)) case ShapeConnectorCondition.None: Func(_shpData); break;
{ case ShapeConnectorCondition.Wrists: Func(_wristConnectors); break;
if (!entry.Value) case ShapeConnectorCondition.Waist: Func(_waistConnectors); break;
case ShapeConnectorCondition.Ankles: Func(_ankleConnectors); break;
}
return; return;
shapes = new Dictionary<ShapeString, ShpHashSet>();
_conditionalSet.Add(identifier.ShapeCondition, shapes);
}
Func(shapes);
}
else
{
Func(_shpData);
}
void Func(Dictionary<ShapeString, ShpHashSet> dict) void Func(Dictionary<ShapeString, ShpHashSet> dict)
{ {
if (!dict.TryGetValue(identifier.Shape, out var value)) if (!dict.TryGetValue(identifier.Shape, out var value))
@ -136,22 +129,19 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
dict.Add(identifier.Shape, value); dict.Add(identifier.Shape, value);
} }
value.TrySet(identifier.Slot, identifier.Id, entry); if (value.TrySet(identifier.Slot, identifier.Id, entry))
++EnabledCount;
} }
} }
protected override void RevertModInternal(ShpIdentifier identifier) protected override void RevertModInternal(ShpIdentifier identifier)
{ {
if (identifier.ShapeCondition.Length > 0) switch (identifier.ConnectorCondition)
{ {
if (!_conditionalSet.TryGetValue(identifier.ShapeCondition, out var shapes)) case ShapeConnectorCondition.None: Func(_shpData); break;
return; case ShapeConnectorCondition.Wrists: Func(_wristConnectors); break;
case ShapeConnectorCondition.Waist: Func(_waistConnectors); break;
Func(shapes); case ShapeConnectorCondition.Ankles: Func(_ankleConnectors); break;
}
else
{
Func(_shpData);
} }
return; return;
@ -162,7 +152,10 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
return; return;
if (value.TrySet(identifier.Slot, identifier.Id, ShpEntry.False) && value.IsEmpty) if (value.TrySet(identifier.Slot, identifier.Id, ShpEntry.False) && value.IsEmpty)
{
--EnabledCount;
_shpData.Remove(identifier.Shape); _shpData.Remove(identifier.Shape);
} }
} }
}
} }

View file

@ -1,4 +1,5 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -7,7 +8,16 @@ using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeString Shape, ShapeString ShapeCondition) [JsonConverter(typeof(StringEnumConverter))]
public enum ShapeConnectorCondition : byte
{
None = 0,
Wrists = 1,
Waist = 2,
Ankles = 3,
}
public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeString Shape, ShapeConnectorCondition ConnectorCondition)
: IComparable<ShpIdentifier>, IMetaIdentifier : IComparable<ShpIdentifier>, IMetaIdentifier
{ {
public int CompareTo(ShpIdentifier other) public int CompareTo(ShpIdentifier other)
@ -34,11 +44,11 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
return 1; return 1;
} }
var shapeComparison = Shape.CompareTo(other.Shape); var conditionComparison = ConnectorCondition.CompareTo(other.ConnectorCondition);
if (shapeComparison is not 0) if (conditionComparison is not 0)
return shapeComparison; return conditionComparison;
return ShapeCondition.CompareTo(other.ShapeCondition); return Shape.CompareTo(other.Shape);
} }
@ -62,9 +72,13 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
sb.Append("All IDs"); sb.Append("All IDs");
} }
if (ShapeCondition.Length > 0) switch (ConnectorCondition)
sb.Append(" - ") {
.Append(ShapeCondition); case ShapeConnectorCondition.Wrists: sb.Append(" - Wrist Connector"); break;
case ShapeConnectorCondition.Waist: sb.Append(" - Waist Connector"); break;
case ShapeConnectorCondition.Ankles: sb.Append(" - Ankle Connector"); break;
}
return sb.ToString(); return sb.ToString();
} }
@ -87,19 +101,15 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
if (!ValidateCustomShapeString(Shape)) if (!ValidateCustomShapeString(Shape))
return false; return false;
if (ShapeCondition.Length is 0) if (!Enum.IsDefined(ConnectorCondition))
return true;
if (!ValidateCustomShapeString(ShapeCondition))
return false; return false;
return Slot switch return ConnectorCondition switch
{ {
HumanSlot.Hands when ShapeCondition.IsWrist() => true, ShapeConnectorCondition.None => true,
HumanSlot.Body when ShapeCondition.IsWrist() || ShapeCondition.IsWaist() => true, ShapeConnectorCondition.Wrists => Slot is HumanSlot.Body or HumanSlot.Hands or HumanSlot.Unknown,
HumanSlot.Legs when ShapeCondition.IsWaist() || ShapeCondition.IsAnkle() => true, ShapeConnectorCondition.Waist => Slot is HumanSlot.Body or HumanSlot.Legs or HumanSlot.Unknown,
HumanSlot.Feet when ShapeCondition.IsAnkle() => true, ShapeConnectorCondition.Ankles => Slot is HumanSlot.Legs or HumanSlot.Feet or HumanSlot.Unknown,
HumanSlot.Unknown when ShapeCondition.IsWrist() || ShapeCondition.IsWaist() || ShapeCondition.IsAnkle() => true,
_ => false, _ => false,
}; };
} }
@ -145,8 +155,8 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
if (Id.HasValue) if (Id.HasValue)
jObj["Id"] = Id.Value.Id.ToString(); jObj["Id"] = Id.Value.Id.ToString();
jObj["Shape"] = Shape.ToString(); jObj["Shape"] = Shape.ToString();
if (ShapeCondition.Length > 0) if (ConnectorCondition is not ShapeConnectorCondition.None)
jObj["ShapeCondition"] = ShapeCondition.ToString(); jObj["ConnectorCondition"] = ConnectorCondition.ToString();
return jObj; return jObj;
} }
@ -158,9 +168,8 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
var slot = jObj["Slot"]?.ToObject<HumanSlot>() ?? HumanSlot.Unknown; var slot = jObj["Slot"]?.ToObject<HumanSlot>() ?? HumanSlot.Unknown;
var id = jObj["Id"]?.ToObject<ushort>(); var id = jObj["Id"]?.ToObject<ushort>();
var shapeCondition = jObj["ShapeCondition"]?.ToObject<string>(); var connectorCondition = jObj["ConnectorCondition"]?.ToObject<ShapeConnectorCondition>() ?? ShapeConnectorCondition.None;
var shapeConditionString = shapeCondition is null || !ShapeString.TryRead(shapeCondition, out var s) ? ShapeString.Empty : s; var identifier = new ShpIdentifier(slot, id, shapeString, connectorCondition);
var identifier = new ShpIdentifier(slot, id, shapeString, shapeConditionString);
return identifier.Validate() ? identifier : null; return identifier.Validate() ? identifier : null;
} }

View file

@ -1,4 +1,3 @@
using System.Reflection.Metadata.Ecma335;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Cache; using Penumbra.Collections.Cache;
@ -80,8 +79,7 @@ public class ShapeManager : IRequiredService, IDisposable
{ {
_temporaryIndices[i].TryAdd(shapeString, index); _temporaryIndices[i].TryAdd(shapeString, index);
_temporaryMasks[i] |= (ushort)(1 << index); _temporaryMasks[i] |= (ushort)(1 << index);
if (cache.State.Count > 0 if (cache.ShouldBeEnabled(shapeString, modelIndex, _ids[(int)modelIndex]))
&& cache.ShouldBeEnabled(shapeString, modelIndex, _ids[(int)modelIndex]))
_temporaryValues[i] |= (ushort)(1 << index); _temporaryValues[i] |= (ushort)(1 << index);
} }
else else
@ -102,14 +100,14 @@ public class ShapeManager : IRequiredService, IDisposable
{ {
_temporaryValues[1] |= 1u << topIndex; _temporaryValues[1] |= 1u << topIndex;
_temporaryValues[2] |= 1u << handIndex; _temporaryValues[2] |= 1u << handIndex;
CheckCondition(shape, HumanSlot.Body, HumanSlot.Hands, 1, 2); CheckCondition(cache.State(ShapeConnectorCondition.Wrists), HumanSlot.Body, HumanSlot.Hands, 1, 2);
} }
if (shape.IsWaist() && _temporaryIndices[3].TryGetValue(shape, out var legIndex)) if (shape.IsWaist() && _temporaryIndices[3].TryGetValue(shape, out var legIndex))
{ {
_temporaryValues[1] |= 1u << topIndex; _temporaryValues[1] |= 1u << topIndex;
_temporaryValues[3] |= 1u << legIndex; _temporaryValues[3] |= 1u << legIndex;
CheckCondition(shape, HumanSlot.Body, HumanSlot.Legs, 1, 3); CheckCondition(cache.State(ShapeConnectorCondition.Waist), HumanSlot.Body, HumanSlot.Legs, 1, 3);
} }
} }
@ -119,25 +117,23 @@ public class ShapeManager : IRequiredService, IDisposable
{ {
_temporaryValues[3] |= 1u << bottomIndex; _temporaryValues[3] |= 1u << bottomIndex;
_temporaryValues[4] |= 1u << footIndex; _temporaryValues[4] |= 1u << footIndex;
CheckCondition(shape, HumanSlot.Legs, HumanSlot.Feet, 3, 4); CheckCondition(cache.State(ShapeConnectorCondition.Ankles), HumanSlot.Legs, HumanSlot.Feet, 3, 4);
} }
} }
return; return;
void CheckCondition(in ShapeString shape, HumanSlot slot1, HumanSlot slot2, int idx1, int idx2) void CheckCondition(IReadOnlyDictionary<ShapeString, ShpCache.ShpHashSet> dict, HumanSlot slot1, HumanSlot slot2, int idx1, int idx2)
{ {
if (!cache.CheckConditionState(shape, out var dict)) if (dict.Count is 0)
return; return;
foreach (var (subShape, set) in dict) foreach (var (shape, set) in dict)
{ {
if (set.Contains(slot1, _ids[idx1])) if (set.Contains(slot1, _ids[idx1]) && _temporaryIndices[idx1].TryGetValue(shape, out var index1))
if (_temporaryIndices[idx1].TryGetValue(subShape, out var subIndex)) _temporaryValues[idx1] |= 1u << index1;
_temporaryValues[idx1] |= 1u << subIndex; if (set.Contains(slot2, _ids[idx2]) && _temporaryIndices[idx2].TryGetValue(shape, out var index2))
if (set.Contains(slot2, _ids[idx2])) _temporaryValues[idx2] |= 1u << index2;
if (_temporaryIndices[idx2].TryGetValue(subShape, out var subIndex))
_temporaryValues[idx2] |= 1u << subIndex;
} }
} }
} }

View file

@ -20,9 +20,7 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
=> "Shape Keys (SHP)###SHP"u8; => "Shape Keys (SHP)###SHP"u8;
private ShapeString _buffer = ShapeString.TryRead("shpx_"u8, out var s) ? s : ShapeString.Empty; private ShapeString _buffer = ShapeString.TryRead("shpx_"u8, out var s) ? s : ShapeString.Empty;
private ShapeString _conditionBuffer = ShapeString.Empty;
private bool _identifierValid; private bool _identifierValid;
private bool _conditionValid = true;
public override int NumColumns public override int NumColumns
=> 7; => 7;
@ -32,7 +30,7 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
protected override void Initialize() protected override void Initialize()
{ {
Identifier = new ShpIdentifier(HumanSlot.Unknown, null, ShapeString.Empty, ShapeString.Empty); Identifier = new ShpIdentifier(HumanSlot.Unknown, null, ShapeString.Empty, ShapeConnectorCondition.None);
} }
protected override void DrawNew() protected override void DrawNew()
@ -42,7 +40,7 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Shp))); new Lazy<JToken?>(() => MetaDictionary.SerializeTo([], Editor.Shp)));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var canAdd = !Editor.Contains(Identifier) && _identifierValid && _conditionValid; var canAdd = !Editor.Contains(Identifier) && _identifierValid;
var tt = canAdd var tt = canAdd
? "Stage this edit."u8 ? "Stage this edit."u8
: _identifierValid : _identifierValid
@ -69,7 +67,7 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
.OrderBy(kvp => kvp.Key.Shape) .OrderBy(kvp => kvp.Key.Shape)
.ThenBy(kvp => kvp.Key.Slot) .ThenBy(kvp => kvp.Key.Slot)
.ThenBy(kvp => kvp.Key.Id) .ThenBy(kvp => kvp.Key.Id)
.ThenBy(kvp => kvp.Key.ShapeCondition) .ThenBy(kvp => kvp.Key.ConnectorCondition)
.Select(kvp => (kvp.Key, kvp.Value)); .Select(kvp => (kvp.Key, kvp.Value));
protected override int Count protected override int Count
@ -87,7 +85,7 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
changes |= DrawShapeKeyInput(ref identifier, ref _buffer, ref _identifierValid); changes |= DrawShapeKeyInput(ref identifier, ref _buffer, ref _identifierValid);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
changes |= DrawShapeConditionInput(ref identifier, ref _conditionBuffer, ref _conditionValid); changes |= DrawConnectorConditionInput(ref identifier);
return changes; return changes;
} }
@ -109,9 +107,9 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
ImUtf8.TextFramed(identifier.Shape.AsSpan, FrameColor); ImUtf8.TextFramed(identifier.Shape.AsSpan, FrameColor);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (identifier.ShapeCondition.Length > 0) if (identifier.ConnectorCondition is not ShapeConnectorCondition.None)
{ {
ImUtf8.TextFramed(identifier.ShapeCondition.AsSpan, FrameColor); ImUtf8.TextFramed($"{identifier.ConnectorCondition}", FrameColor);
ImUtf8.HoverTooltip("Connector condition for this shape to be activated."); ImUtf8.HoverTooltip("Connector condition for this shape to be activated.");
} }
} }
@ -189,28 +187,19 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
}; };
} }
else else
{
if (_conditionBuffer.Length > 0
&& (_conditionBuffer.IsAnkle() && slot is not HumanSlot.Feet and not HumanSlot.Legs
|| _conditionBuffer.IsWrist() && slot is not HumanSlot.Hands and not HumanSlot.Body
|| _conditionBuffer.IsWaist() && slot is not HumanSlot.Body and not HumanSlot.Legs))
{ {
identifier = identifier with identifier = identifier with
{ {
Slot = slot, Slot = slot,
ShapeCondition = ShapeString.Empty, ConnectorCondition = Identifier.ConnectorCondition switch
};
_conditionValid = false;
}
else
{ {
identifier = identifier with ShapeConnectorCondition.Wrists when slot is HumanSlot.Body or HumanSlot.Hands => ShapeConnectorCondition.Wrists,
{ ShapeConnectorCondition.Waist when slot is HumanSlot.Body or HumanSlot.Legs => ShapeConnectorCondition.Waist,
Slot = slot, ShapeConnectorCondition.Ankles when slot is HumanSlot.Legs or HumanSlot.Feet => ShapeConnectorCondition.Ankles,
ShapeCondition = _conditionBuffer, _ => ShapeConnectorCondition.None,
},
}; };
_conditionValid = true; ret = true;
}
} }
} }
} }
@ -241,30 +230,40 @@ public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile
return ret; return ret;
} }
public static unsafe bool DrawShapeConditionInput(ref ShpIdentifier identifier, ref ShapeString buffer, ref bool valid, public static unsafe bool DrawConnectorConditionInput(ref ShpIdentifier identifier, float unscaledWidth = 150)
float unscaledWidth = 150)
{ {
var ret = false; var ret = false;
var ptr = Unsafe.AsPointer(ref buffer);
var span = new Span<byte>(ptr, ShapeString.MaxLength + 1);
using (new ImRaii.ColorStyle().Push(ImGuiCol.Border, Colors.RegexWarningBorder, !valid).Push(ImGuiStyleVar.FrameBorderSize, 1f, !valid))
{
ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale);
if (ImUtf8.InputText("##shpCondition"u8, span, out int newLength, "Shape Condition..."u8)) var (showWrists, showWaist, showAnkles, disable) = identifier.Slot switch
{ {
buffer.ForceLength((byte)newLength); HumanSlot.Unknown => (true, true, true, false),
valid = ShpIdentifier.ValidateCustomShapeString(buffer) HumanSlot.Body => (true, true, false, false),
&& (buffer.IsAnkle() && identifier.Slot is HumanSlot.Unknown or HumanSlot.Feet or HumanSlot.Legs HumanSlot.Legs => (false, true, true, false),
|| buffer.IsWaist() && identifier.Slot is HumanSlot.Unknown or HumanSlot.Body or HumanSlot.Legs HumanSlot.Hands => (true, false, false, false),
|| buffer.IsWrist() && identifier.Slot is HumanSlot.Unknown or HumanSlot.Body or HumanSlot.Hands); HumanSlot.Feet => (false, false, true, false),
if (valid) _ => (false, false, false, true),
identifier = identifier with { ShapeCondition = buffer }; };
ret = true; using var disabled = ImRaii.Disabled(disable);
using (var combo = ImUtf8.Combo("##shpCondition"u8, $"{identifier.ConnectorCondition}"))
{
if (combo)
{
if (ImUtf8.Selectable("None"u8, identifier.ConnectorCondition is ShapeConnectorCondition.None))
identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.None };
if (showWrists && ImUtf8.Selectable("Wrists"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Wrists))
identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Wrists };
if (showWaist && ImUtf8.Selectable("Waist"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Waist))
identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Waist };
if (showAnkles && ImUtf8.Selectable("Ankles"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Ankles))
identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Ankles };
} }
} }
ImUtf8.HoverTooltip( ImUtf8.HoverTooltip(
"Supported conditional shape keys need to have the format `shpx_an_*` (Legs or Feet), `shpx_wr_*` (Body or Hands), or `shpx_wa_*` (Body or Legs) and a maximum length of 30 characters."u8); "Only activate this shape key if any custom connector shape keys (shpx_[wr|wa|an]_*) are also enabled through matching attributes."u8);
return ret; return ret;
} }

View file

@ -36,41 +36,6 @@ using MdlMaterialEditor = Penumbra.Mods.Editor.MdlMaterialEditor;
namespace Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.AdvancedWindow;
public sealed class OptionSelectCombo(ModEditor editor, ModEditWindow window)
: FilterComboCache<(string FullName, (int Group, int Data) Index)>(
() => window.Mod!.AllDataContainers.Select(c => (c.GetFullName(), c.GetDataIndices())).ToList(), MouseWheelType.Control, Penumbra.Log)
{
private ImRaii.ColorStyle _border;
protected override void DrawCombo(string label, string preview, string tooltip, int currentSelected, float previewWidth, float itemHeight,
ImGuiComboFlags flags)
{
_border = ImRaii.PushFrameBorder(ImUtf8.GlobalScale, ColorId.FolderLine.Value());
base.DrawCombo(label, preview, tooltip, currentSelected, previewWidth, itemHeight, flags);
_border.Dispose();
}
protected override void DrawFilter(int currentSelected, float width)
{
_border.Dispose();
base.DrawFilter(currentSelected, width);
}
public bool Draw(float width)
{
var flags = window.Mod!.AllDataContainers.Count() switch
{
0 => ImGuiComboFlags.NoArrowButton,
> 8 => ImGuiComboFlags.HeightLargest,
_ => ImGuiComboFlags.None,
};
return Draw("##optionSelector", editor.Option!.GetFullName(), string.Empty, width, ImGui.GetTextLineHeight(), flags);
}
protected override bool DrawSelectable(int globalIdx, bool selected)
=> ImUtf8.Selectable(Items[globalIdx].FullName, selected);
}
public partial class ModEditWindow : Window, IDisposable, IUiService public partial class ModEditWindow : Window, IDisposable, IUiService
{ {
private const string WindowBaseLabel = "###SubModEdit"; private const string WindowBaseLabel = "###SubModEdit";

View file

@ -8,6 +8,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving; using Penumbra.Interop.PathResolving;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
namespace Penumbra.UI.Tabs.Debug; namespace Penumbra.UI.Tabs.Debug;
@ -52,17 +53,11 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
ImUtf8.TableSetupColumn("Enabled"u8, ImGuiTableColumnFlags.WidthStretch); ImUtf8.TableSetupColumn("Enabled"u8, ImGuiTableColumnFlags.WidthStretch);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State) foreach (var condition in Enum.GetValues<ShapeConnectorCondition>())
{ {
ImGui.TableNextColumn(); foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State(condition))
DrawShape(shape, set);
}
foreach (var (condition, dict) in data.ModCollection.MetaCache!.Shp.ConditionState)
{ {
foreach (var (shape, set) in dict) ImUtf8.DrawTableColumn(condition.ToString());
{
ImUtf8.DrawTableColumn(condition.AsSpan);
DrawShape(shape, set); DrawShape(shape, set);
} }
} }

View file

@ -29,6 +29,10 @@
"$anchor": "SubRace", "$anchor": "SubRace",
"enum": [ "Unknown", "Midlander", "Highlander", "Wildwood", "Duskwight", "Plainsfolk", "Dunesfolk", "SeekerOfTheSun", "KeeperOfTheMoon", "Seawolf", "Hellsguard", "Raen", "Xaela", "Helion", "Lost", "Rava", "Veena" ] "enum": [ "Unknown", "Midlander", "Highlander", "Wildwood", "Duskwight", "Plainsfolk", "Dunesfolk", "SeekerOfTheSun", "KeeperOfTheMoon", "Seawolf", "Hellsguard", "Raen", "Xaela", "Helion", "Lost", "Rava", "Veena" ]
}, },
"ShapeConnectorCondition": {
"$anchor": "ShapeConnectorCondition",
"enum": [ "None", "Wrists", "Waist", "Ankles" ]
},
"U8": { "U8": {
"$anchor": "U8", "$anchor": "U8",
"oneOf": [ "oneOf": [

View file

@ -17,11 +17,8 @@
"maxLength": 30, "maxLength": 30,
"pattern": "^shpx_" "pattern": "^shpx_"
}, },
"ShapeCondition": { "ConnectorCondition": {
"type": "string", "$ref": "meta_enums.json#ShapeConnectorCondition"
"minLength": 8,
"maxLength": 30,
"pattern": "^shpx_(wa|an|wr)_"
} }
}, },
"required": [ "required": [