mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-31 21:03:48 +01:00
Add conditional connector shapes.
This commit is contained in:
parent
c0dcfdd835
commit
f1448ed947
7 changed files with 360 additions and 120 deletions
|
|
@ -7,7 +7,7 @@ using Penumbra.Interop.Structs;
|
|||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeString Shape)
|
||||
public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeString Shape, ShapeString ShapeCondition)
|
||||
: IComparable<ShpIdentifier>, IMetaIdentifier
|
||||
{
|
||||
public int CompareTo(ShpIdentifier other)
|
||||
|
|
@ -34,12 +34,39 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
|
|||
return 1;
|
||||
}
|
||||
|
||||
var shapeComparison = Shape.CompareTo(other.Shape);
|
||||
if (shapeComparison is not 0)
|
||||
return shapeComparison;
|
||||
|
||||
return Shape.CompareTo(other.Shape);
|
||||
return ShapeCondition.CompareTo(other.ShapeCondition);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
=> $"Shp - {Shape}{(Slot is HumanSlot.Unknown ? " - All Slots & IDs" : $" - {Slot.ToName()}{(Id.HasValue ? $" - {Id.Value.Id}" : " - All IDs")}")}";
|
||||
{
|
||||
var sb = new StringBuilder(64);
|
||||
sb.Append("Shp - ")
|
||||
.Append(Shape);
|
||||
if (Slot is HumanSlot.Unknown)
|
||||
{
|
||||
sb.Append(" - All Slots & IDs");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" - ")
|
||||
.Append(Slot.ToName())
|
||||
.Append(" - ");
|
||||
if (Id.HasValue)
|
||||
sb.Append(Id.Value.Id);
|
||||
else
|
||||
sb.Append("All IDs");
|
||||
}
|
||||
|
||||
if (ShapeCondition.Length > 0)
|
||||
sb.Append(" - ")
|
||||
.Append(ShapeCondition);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData> changedItems)
|
||||
{
|
||||
|
|
@ -57,7 +84,24 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
|
|||
if (Slot is HumanSlot.Unknown && Id is not null)
|
||||
return false;
|
||||
|
||||
return ValidateCustomShapeString(Shape);
|
||||
if (!ValidateCustomShapeString(Shape))
|
||||
return false;
|
||||
|
||||
if (ShapeCondition.Length is 0)
|
||||
return true;
|
||||
|
||||
if (!ValidateCustomShapeString(ShapeCondition))
|
||||
return false;
|
||||
|
||||
return Slot switch
|
||||
{
|
||||
HumanSlot.Hands when ShapeCondition.IsWrist() => true,
|
||||
HumanSlot.Body when ShapeCondition.IsWrist() || ShapeCondition.IsWaist() => true,
|
||||
HumanSlot.Legs when ShapeCondition.IsWaist() || ShapeCondition.IsAnkle() => true,
|
||||
HumanSlot.Feet when ShapeCondition.IsAnkle() => true,
|
||||
HumanSlot.Unknown when ShapeCondition.IsWrist() || ShapeCondition.IsWaist() || ShapeCondition.IsAnkle() => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static unsafe bool ValidateCustomShapeString(byte* shape)
|
||||
|
|
@ -101,18 +145,22 @@ public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, Shape
|
|||
if (Id.HasValue)
|
||||
jObj["Id"] = Id.Value.Id.ToString();
|
||||
jObj["Shape"] = Shape.ToString();
|
||||
if (ShapeCondition.Length > 0)
|
||||
jObj["ShapeCondition"] = ShapeCondition.ToString();
|
||||
return jObj;
|
||||
}
|
||||
|
||||
public static ShpIdentifier? FromJson(JObject jObj)
|
||||
{
|
||||
var slot = jObj["Slot"]?.ToObject<HumanSlot>() ?? HumanSlot.Unknown;
|
||||
var id = jObj["Id"]?.ToObject<ushort>();
|
||||
var shape = jObj["Shape"]?.ToObject<string>();
|
||||
if (shape is null || !ShapeString.TryRead(shape, out var shapeString))
|
||||
return null;
|
||||
|
||||
var identifier = new ShpIdentifier(slot, id, shapeString);
|
||||
var slot = jObj["Slot"]?.ToObject<HumanSlot>() ?? HumanSlot.Unknown;
|
||||
var id = jObj["Id"]?.ToObject<ushort>();
|
||||
var shapeCondition = jObj["ShapeCondition"]?.ToObject<string>();
|
||||
var shapeConditionString = shapeCondition is null || !ShapeString.TryRead(shapeCondition, out var s) ? ShapeString.Empty : s;
|
||||
var identifier = new ShpIdentifier(slot, id, shapeString, shapeConditionString);
|
||||
return identifier.Validate() ? identifier : null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System.Reflection.Metadata.Ecma335;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Cache;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Hooks.PostProcessing;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
|
|
@ -30,15 +32,19 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
private readonly Dictionary<ShapeString, short>[] _temporaryIndices =
|
||||
Enumerable.Range(0, NumSlots).Select(_ => new Dictionary<ShapeString, short>()).ToArray();
|
||||
|
||||
private readonly uint[] _temporaryMasks = new uint[NumSlots];
|
||||
private readonly uint[] _temporaryValues = new uint[NumSlots];
|
||||
private readonly uint[] _temporaryMasks = new uint[NumSlots];
|
||||
private readonly uint[] _temporaryValues = new uint[NumSlots];
|
||||
private readonly PrimaryId[] _ids = new PrimaryId[ModelSlotSize];
|
||||
|
||||
public void Dispose()
|
||||
=> _attributeHook.Unsubscribe(OnAttributeComputed);
|
||||
|
||||
private unsafe void OnAttributeComputed(Actor actor, Model model, ModCollection collection)
|
||||
{
|
||||
ComputeCache(model, collection);
|
||||
if (!collection.HasCache)
|
||||
return;
|
||||
|
||||
ComputeCache(model, collection.MetaCache!.Shp);
|
||||
for (var i = 0; i < NumSlots; ++i)
|
||||
{
|
||||
if (_temporaryMasks[i] is 0)
|
||||
|
|
@ -52,11 +58,8 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe void ComputeCache(Model human, ModCollection collection)
|
||||
private unsafe void ComputeCache(Model human, ShpCache cache)
|
||||
{
|
||||
if (!collection.HasCache)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < NumSlots; ++i)
|
||||
{
|
||||
_temporaryMasks[i] = 0;
|
||||
|
|
@ -68,6 +71,8 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
if (model is null || model->ModelResourceHandle is null)
|
||||
continue;
|
||||
|
||||
_ids[(int)modelIndex] = human.GetArmorChanged(modelIndex).Set;
|
||||
|
||||
ref var shapes = ref model->ModelResourceHandle->Shapes;
|
||||
foreach (var (shape, index) in shapes.Where(kvp => ShpIdentifier.ValidateCustomShapeString(kvp.Key.Value)))
|
||||
{
|
||||
|
|
@ -75,8 +80,8 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
{
|
||||
_temporaryIndices[i].TryAdd(shapeString, index);
|
||||
_temporaryMasks[i] |= (ushort)(1 << index);
|
||||
if (collection.MetaCache!.Shp.State.Count > 0
|
||||
&& collection.MetaCache!.Shp.ShouldBeEnabled(shapeString, modelIndex, human.GetArmorChanged(modelIndex).Set))
|
||||
if (cache.State.Count > 0
|
||||
&& cache.ShouldBeEnabled(shapeString, modelIndex, _ids[(int)modelIndex]))
|
||||
_temporaryValues[i] |= (ushort)(1 << index);
|
||||
}
|
||||
else
|
||||
|
|
@ -86,37 +91,54 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
UpdateDefaultMasks();
|
||||
UpdateDefaultMasks(cache);
|
||||
}
|
||||
|
||||
private void UpdateDefaultMasks()
|
||||
private void UpdateDefaultMasks(ShpCache cache)
|
||||
{
|
||||
foreach (var (shape, topIndex) in _temporaryIndices[1])
|
||||
{
|
||||
if (CheckCenter(shape, 'w', 'r') && _temporaryIndices[2].TryGetValue(shape, out var handIndex))
|
||||
if (shape.IsWrist() && _temporaryIndices[2].TryGetValue(shape, out var handIndex))
|
||||
{
|
||||
_temporaryValues[1] |= 1u << topIndex;
|
||||
_temporaryValues[2] |= 1u << handIndex;
|
||||
CheckCondition(shape, HumanSlot.Body, HumanSlot.Hands, 1, 2);
|
||||
}
|
||||
|
||||
if (CheckCenter(shape, 'w', 'a') && _temporaryIndices[3].TryGetValue(shape, out var legIndex))
|
||||
if (shape.IsWaist() && _temporaryIndices[3].TryGetValue(shape, out var legIndex))
|
||||
{
|
||||
_temporaryValues[1] |= 1u << topIndex;
|
||||
_temporaryValues[3] |= 1u << legIndex;
|
||||
CheckCondition(shape, HumanSlot.Body, HumanSlot.Legs, 1, 3);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (shape, bottomIndex) in _temporaryIndices[3])
|
||||
{
|
||||
if (CheckCenter(shape, 'a', 'n') && _temporaryIndices[4].TryGetValue(shape, out var footIndex))
|
||||
if (shape.IsAnkle() && _temporaryIndices[4].TryGetValue(shape, out var footIndex))
|
||||
{
|
||||
_temporaryValues[3] |= 1u << bottomIndex;
|
||||
_temporaryValues[4] |= 1u << footIndex;
|
||||
CheckCondition(shape, HumanSlot.Legs, HumanSlot.Feet, 3, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void CheckCondition(in ShapeString shape, HumanSlot slot1, HumanSlot slot2, int idx1, int idx2)
|
||||
{
|
||||
if (!cache.CheckConditionState(shape, out var dict))
|
||||
return;
|
||||
|
||||
foreach (var (subShape, set) in dict)
|
||||
{
|
||||
if (set.Contains(slot1, _ids[idx1]))
|
||||
if (_temporaryIndices[idx1].TryGetValue(subShape, out var subIndex))
|
||||
_temporaryValues[idx1] |= 1u << subIndex;
|
||||
if (set.Contains(slot2, _ids[idx2]))
|
||||
if (_temporaryIndices[idx2].TryGetValue(subShape, out var subIndex))
|
||||
_temporaryValues[idx2] |= 1u << subIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool CheckCenter(in ShapeString shape, char first, char second)
|
||||
=> shape.Length > 8 && shape[4] == first && shape[5] == second && shape[6] is (byte)'_';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ public struct ShapeString : IEquatable<ShapeString>, IComparable<ShapeString>
|
|||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public bool IsAnkle()
|
||||
=> CheckCenter('a', 'n');
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public bool IsWaist()
|
||||
=> CheckCenter('w', 'a');
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public bool IsWrist()
|
||||
=> CheckCenter('w', 'r');
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private bool CheckCenter(char first, char second)
|
||||
=> Length > 8 && _buffer[5] == first && _buffer[6] == second && _buffer[7] is (byte)'_';
|
||||
|
||||
public bool Equals(ShapeString other)
|
||||
=> Length == other.Length && _buffer[..Length].SequenceEqual(other._buffer[..Length]);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue