mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-01-02 13:53:42 +01:00
Add shape meta manipulations and rework attribute hook.
This commit is contained in:
parent
0adec35848
commit
6ad0b4299a
23 changed files with 900 additions and 298 deletions
|
|
@ -15,6 +15,7 @@ public enum MetaManipulationType : byte
|
|||
Rsp = 6,
|
||||
GlobalEqp = 7,
|
||||
Atch = 8,
|
||||
Shp = 9,
|
||||
}
|
||||
|
||||
public interface IMetaIdentifier
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class MetaDictionary
|
|||
private readonly Dictionary<RspIdentifier, RspEntry> _rsp = [];
|
||||
private readonly Dictionary<GmpIdentifier, GmpEntry> _gmp = [];
|
||||
private readonly Dictionary<AtchIdentifier, AtchEntry> _atch = [];
|
||||
private readonly Dictionary<ShpIdentifier, ShpEntry> _shp = [];
|
||||
private readonly HashSet<GlobalEqpManipulation> _globalEqp = [];
|
||||
|
||||
public IReadOnlyDictionary<ImcIdentifier, ImcEntry> Imc
|
||||
|
|
@ -41,6 +42,9 @@ public class MetaDictionary
|
|||
public IReadOnlyDictionary<AtchIdentifier, AtchEntry> Atch
|
||||
=> _atch;
|
||||
|
||||
public IReadOnlyDictionary<ShpIdentifier, ShpEntry> Shp
|
||||
=> _shp;
|
||||
|
||||
public IReadOnlySet<GlobalEqpManipulation> GlobalEqp
|
||||
=> _globalEqp;
|
||||
|
||||
|
|
@ -56,6 +60,7 @@ public class MetaDictionary
|
|||
MetaManipulationType.Gmp => _gmp.Count,
|
||||
MetaManipulationType.Rsp => _rsp.Count,
|
||||
MetaManipulationType.Atch => _atch.Count,
|
||||
MetaManipulationType.Shp => _shp.Count,
|
||||
MetaManipulationType.GlobalEqp => _globalEqp.Count,
|
||||
_ => 0,
|
||||
};
|
||||
|
|
@ -70,6 +75,7 @@ public class MetaDictionary
|
|||
GmpIdentifier i => _gmp.ContainsKey(i),
|
||||
ImcIdentifier i => _imc.ContainsKey(i),
|
||||
AtchIdentifier i => _atch.ContainsKey(i),
|
||||
ShpIdentifier i => _shp.ContainsKey(i),
|
||||
RspIdentifier i => _rsp.ContainsKey(i),
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -84,6 +90,7 @@ public class MetaDictionary
|
|||
_rsp.Clear();
|
||||
_gmp.Clear();
|
||||
_atch.Clear();
|
||||
_shp.Clear();
|
||||
_globalEqp.Clear();
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +115,7 @@ public class MetaDictionary
|
|||
&& _rsp.SetEquals(other._rsp)
|
||||
&& _gmp.SetEquals(other._gmp)
|
||||
&& _atch.SetEquals(other._atch)
|
||||
&& _shp.SetEquals(other._shp)
|
||||
&& _globalEqp.SetEquals(other._globalEqp);
|
||||
|
||||
public IEnumerable<IMetaIdentifier> Identifiers
|
||||
|
|
@ -118,6 +126,7 @@ public class MetaDictionary
|
|||
.Concat(_gmp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_rsp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_atch.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_shp.Keys.Cast<IMetaIdentifier>())
|
||||
.Concat(_globalEqp.Cast<IMetaIdentifier>());
|
||||
|
||||
#region TryAdd
|
||||
|
|
@ -191,6 +200,15 @@ public class MetaDictionary
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool TryAdd(ShpIdentifier identifier, in ShpEntry entry)
|
||||
{
|
||||
if (!_shp.TryAdd(identifier, entry))
|
||||
return false;
|
||||
|
||||
++Count;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryAdd(GlobalEqpManipulation identifier)
|
||||
{
|
||||
if (!_globalEqp.Add(identifier))
|
||||
|
|
@ -273,6 +291,15 @@ public class MetaDictionary
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool Update(ShpIdentifier identifier, in ShpEntry entry)
|
||||
{
|
||||
if (!_shp.ContainsKey(identifier))
|
||||
return false;
|
||||
|
||||
_shp[identifier] = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TryGetValue
|
||||
|
|
@ -298,6 +325,9 @@ public class MetaDictionary
|
|||
public bool TryGetValue(AtchIdentifier identifier, out AtchEntry value)
|
||||
=> _atch.TryGetValue(identifier, out value);
|
||||
|
||||
public bool TryGetValue(ShpIdentifier identifier, out ShpEntry value)
|
||||
=> _shp.TryGetValue(identifier, out value);
|
||||
|
||||
#endregion
|
||||
|
||||
public bool Remove(IMetaIdentifier identifier)
|
||||
|
|
@ -312,6 +342,7 @@ public class MetaDictionary
|
|||
ImcIdentifier i => _imc.Remove(i),
|
||||
RspIdentifier i => _rsp.Remove(i),
|
||||
AtchIdentifier i => _atch.Remove(i),
|
||||
ShpIdentifier i => _shp.Remove(i),
|
||||
_ => false,
|
||||
};
|
||||
if (ret)
|
||||
|
|
@ -344,6 +375,9 @@ public class MetaDictionary
|
|||
foreach (var (identifier, entry) in manips._atch)
|
||||
TryAdd(identifier, entry);
|
||||
|
||||
foreach (var (identifier, entry) in manips._shp)
|
||||
TryAdd(identifier, entry);
|
||||
|
||||
foreach (var identifier in manips._globalEqp)
|
||||
TryAdd(identifier);
|
||||
}
|
||||
|
|
@ -393,13 +427,19 @@ public class MetaDictionary
|
|||
return false;
|
||||
}
|
||||
|
||||
foreach (var (identifier, _) in manips._shp.Where(kvp => !TryAdd(kvp.Key, kvp.Value)))
|
||||
{
|
||||
failedIdentifier = identifier;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var identifier in manips._globalEqp.Where(identifier => !TryAdd(identifier)))
|
||||
{
|
||||
failedIdentifier = identifier;
|
||||
return false;
|
||||
}
|
||||
|
||||
failedIdentifier = default;
|
||||
failedIdentifier = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -412,8 +452,9 @@ public class MetaDictionary
|
|||
_rsp.SetTo(other._rsp);
|
||||
_gmp.SetTo(other._gmp);
|
||||
_atch.SetTo(other._atch);
|
||||
_shp.SetTo(other._shp);
|
||||
_globalEqp.SetTo(other._globalEqp);
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count;
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _shp.Count + _globalEqp.Count;
|
||||
}
|
||||
|
||||
public void UpdateTo(MetaDictionary other)
|
||||
|
|
@ -425,8 +466,9 @@ public class MetaDictionary
|
|||
_rsp.UpdateTo(other._rsp);
|
||||
_gmp.UpdateTo(other._gmp);
|
||||
_atch.UpdateTo(other._atch);
|
||||
_shp.UpdateTo(other._shp);
|
||||
_globalEqp.UnionWith(other._globalEqp);
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count;
|
||||
Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _shp.Count + _globalEqp.Count;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -514,6 +556,16 @@ public class MetaDictionary
|
|||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(ShpIdentifier identifier, ShpEntry entry)
|
||||
=> new()
|
||||
{
|
||||
["Type"] = MetaManipulationType.Shp.ToString(),
|
||||
["Manipulation"] = identifier.AddToJson(new JObject
|
||||
{
|
||||
["Entry"] = entry.Value,
|
||||
}),
|
||||
};
|
||||
|
||||
public static JObject Serialize(GlobalEqpManipulation identifier)
|
||||
=> new()
|
||||
{
|
||||
|
|
@ -543,6 +595,8 @@ public class MetaDictionary
|
|||
return Serialize(Unsafe.As<TIdentifier, ImcIdentifier>(ref identifier), Unsafe.As<TEntry, ImcEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(AtchIdentifier) && typeof(TEntry) == typeof(AtchEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, AtchIdentifier>(ref identifier), Unsafe.As<TEntry, AtchEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(ShpIdentifier) && typeof(TEntry) == typeof(ShpEntry))
|
||||
return Serialize(Unsafe.As<TIdentifier, ShpIdentifier>(ref identifier), Unsafe.As<TEntry, ShpEntry>(ref entry));
|
||||
if (typeof(TIdentifier) == typeof(GlobalEqpManipulation))
|
||||
return Serialize(Unsafe.As<TIdentifier, GlobalEqpManipulation>(ref identifier));
|
||||
|
||||
|
|
@ -588,6 +642,7 @@ public class MetaDictionary
|
|||
SerializeTo(array, value._rsp);
|
||||
SerializeTo(array, value._gmp);
|
||||
SerializeTo(array, value._atch);
|
||||
SerializeTo(array, value._shp);
|
||||
SerializeTo(array, value._globalEqp);
|
||||
array.WriteTo(writer);
|
||||
}
|
||||
|
|
@ -685,6 +740,16 @@ public class MetaDictionary
|
|||
Penumbra.Log.Warning("Invalid ATCH Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulationType.Shp:
|
||||
{
|
||||
var identifier = ShpIdentifier.FromJson(manip);
|
||||
var entry = new ShpEntry(manip["Entry"]?.Value<bool>() ?? true);
|
||||
if (identifier.HasValue)
|
||||
dict.TryAdd(identifier.Value, entry);
|
||||
else
|
||||
Penumbra.Log.Warning("Invalid SHP Manipulation encountered.");
|
||||
break;
|
||||
}
|
||||
case MetaManipulationType.GlobalEqp:
|
||||
{
|
||||
var identifier = GlobalEqpManipulation.FromJson(manip);
|
||||
|
|
@ -716,6 +781,7 @@ public class MetaDictionary
|
|||
_gmp = cache.Gmp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_rsp = cache.Rsp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_atch = cache.Atch.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_shp = cache.Shp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry);
|
||||
_globalEqp = cache.GlobalEqp.Select(kvp => kvp.Key).ToHashSet();
|
||||
Count = cache.Count;
|
||||
}
|
||||
|
|
|
|||
157
Penumbra/Meta/Manipulations/ShpIdentifier.cs
Normal file
157
Penumbra/Meta/Manipulations/ShpIdentifier.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
using Lumina.Models.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly record struct ShpIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeString Shape)
|
||||
: IComparable<ShpIdentifier>, IMetaIdentifier
|
||||
{
|
||||
public int CompareTo(ShpIdentifier other)
|
||||
{
|
||||
var slotComparison = Slot.CompareTo(other.Slot);
|
||||
if (slotComparison is not 0)
|
||||
return slotComparison;
|
||||
|
||||
if (Id.HasValue)
|
||||
{
|
||||
if (other.Id.HasValue)
|
||||
{
|
||||
var idComparison = Id.Value.Id.CompareTo(other.Id.Value.Id);
|
||||
if (idComparison is not 0)
|
||||
return idComparison;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (other.Id.HasValue)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return Shape.CompareTo(other.Shape);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Shp - {Shape}{(Slot is HumanSlot.Unknown ? " - All Slots & IDs" : $" - {Slot.ToName()}{(Id.HasValue ? $" - {Id.Value.Id}" : " - All IDs")}")}";
|
||||
|
||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData> changedItems)
|
||||
{
|
||||
// Nothing for now since it depends entirely on the shape key.
|
||||
}
|
||||
|
||||
public MetaIndex FileIndex()
|
||||
=> (MetaIndex)(-1);
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
if (!Enum.IsDefined(Slot) || Slot is HumanSlot.UnkBonus)
|
||||
return false;
|
||||
|
||||
if (Slot is HumanSlot.Unknown && Id is not null)
|
||||
return false;
|
||||
|
||||
return ValidateCustomShapeString(Shape);
|
||||
}
|
||||
|
||||
public static bool ValidateCustomShapeString(ReadOnlySpan<byte> shape)
|
||||
{
|
||||
// "shp_xx_y"
|
||||
if (shape.Length < 8)
|
||||
return false;
|
||||
|
||||
if (shape[0] is not (byte)'s'
|
||||
|| shape[1] is not (byte)'h'
|
||||
|| shape[2] is not (byte)'p'
|
||||
|| shape[3] is not (byte)'_'
|
||||
|| shape[6] is not (byte)'_')
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static unsafe bool ValidateCustomShapeString(byte* shape)
|
||||
{
|
||||
// "shp_xx_y"
|
||||
if (shape is null)
|
||||
return false;
|
||||
|
||||
if (*shape++ is not (byte)'s'
|
||||
|| *shape++ is not (byte)'h'
|
||||
|| *shape++ is not (byte)'p'
|
||||
|| *shape++ is not (byte)'_'
|
||||
|| *shape++ is 0
|
||||
|| *shape++ is 0
|
||||
|| *shape++ is not (byte)'_'
|
||||
|| *shape is 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateCustomShapeString(in ShapeString shape)
|
||||
{
|
||||
// "shp_xx_y"
|
||||
if (shape.Length < 8)
|
||||
return false;
|
||||
|
||||
var span = shape.AsSpan;
|
||||
if (span[0] is not (byte)'s'
|
||||
|| span[1] is not (byte)'h'
|
||||
|| span[2] is not (byte)'p'
|
||||
|| span[3] is not (byte)'_'
|
||||
|| span[6] is not (byte)'_')
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public JObject AddToJson(JObject jObj)
|
||||
{
|
||||
if (Slot is not HumanSlot.Unknown)
|
||||
jObj["Slot"] = Slot.ToString();
|
||||
if (Id.HasValue)
|
||||
jObj["Id"] = Id.Value.Id.ToString();
|
||||
jObj["Shape"] = Shape.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);
|
||||
return identifier.Validate() ? identifier : null;
|
||||
}
|
||||
|
||||
public MetaManipulationType Type
|
||||
=> MetaManipulationType.Shp;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public readonly record struct ShpEntry(bool Value)
|
||||
{
|
||||
public static readonly ShpEntry True = new(true);
|
||||
public static readonly ShpEntry False = new(false);
|
||||
|
||||
private class Converter : JsonConverter<ShpEntry>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, ShpEntry value, JsonSerializer serializer)
|
||||
=> serializer.Serialize(writer, value.Value);
|
||||
|
||||
public override ShpEntry ReadJson(JsonReader reader, Type objectType, ShpEntry existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
=> new(serializer.Deserialize<bool>(reader));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,30 @@
|
|||
using System.Reflection.Metadata.Ecma335;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Interop.Hooks.PostProcessing;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
public class ShapeManager : IRequiredService, IDisposable
|
||||
{
|
||||
public const int NumSlots = 4;
|
||||
private readonly CommunicatorService _communicator;
|
||||
public const int NumSlots = 14;
|
||||
public const int ModelSlotSize = 18;
|
||||
private readonly AttributeHook _attributeHook;
|
||||
|
||||
private static ReadOnlySpan<byte> UsedModels
|
||||
=> [1, 2, 3, 4];
|
||||
public static ReadOnlySpan<HumanSlot> UsedModels
|
||||
=>
|
||||
[
|
||||
HumanSlot.Head, HumanSlot.Body, HumanSlot.Hands, HumanSlot.Legs, HumanSlot.Feet, HumanSlot.Ears, HumanSlot.Neck, HumanSlot.Wrists,
|
||||
HumanSlot.RFinger, HumanSlot.LFinger, HumanSlot.Glasses, HumanSlot.Hair, HumanSlot.Face, HumanSlot.Ear,
|
||||
];
|
||||
|
||||
public ShapeManager(CommunicatorService communicator)
|
||||
public ShapeManager(AttributeHook attributeHook)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_communicator.ModelAttributeComputed.Subscribe(OnAttributeComputed, ModelAttributeComputed.Priority.ShapeManager);
|
||||
_attributeHook = attributeHook;
|
||||
_attributeHook.Subscribe(OnAttributeComputed, AttributeHook.Priority.ShapeManager);
|
||||
}
|
||||
|
||||
private readonly Dictionary<ShapeString, short>[] _temporaryIndices =
|
||||
|
|
@ -27,38 +33,30 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
private readonly uint[] _temporaryMasks = new uint[NumSlots];
|
||||
private readonly uint[] _temporaryValues = new uint[NumSlots];
|
||||
|
||||
private unsafe void OnAttributeComputed(Actor actor, Model model, ModCollection collection, HumanSlot slot)
|
||||
{
|
||||
int index;
|
||||
switch (slot)
|
||||
{
|
||||
case HumanSlot.Unknown:
|
||||
ResetCache(model);
|
||||
return;
|
||||
case HumanSlot.Body: index = 0; break;
|
||||
case HumanSlot.Hands: index = 1; break;
|
||||
case HumanSlot.Legs: index = 2; break;
|
||||
case HumanSlot.Feet: index = 3; break;
|
||||
default: return;
|
||||
}
|
||||
public void Dispose()
|
||||
=> _attributeHook.Unsubscribe(OnAttributeComputed);
|
||||
|
||||
if (_temporaryMasks[index] is 0)
|
||||
private unsafe void OnAttributeComputed(Actor actor, Model model, ModCollection collection)
|
||||
{
|
||||
ComputeCache(model, collection);
|
||||
for (var i = 0; i < NumSlots; ++i)
|
||||
{
|
||||
if (_temporaryMasks[i] is 0)
|
||||
continue;
|
||||
|
||||
var modelIndex = UsedModels[i];
|
||||
var currentMask = model.AsHuman->Models[modelIndex.ToIndex()]->EnabledShapeKeyIndexMask;
|
||||
var newMask = (currentMask & ~_temporaryMasks[i]) | _temporaryValues[i];
|
||||
Penumbra.Log.Excessive($"Changed Model Mask from {currentMask:X} to {newMask:X}.");
|
||||
model.AsHuman->Models[modelIndex.ToIndex()]->EnabledShapeKeyIndexMask = newMask;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ComputeCache(Model human, ModCollection collection)
|
||||
{
|
||||
if (!collection.HasCache)
|
||||
return;
|
||||
|
||||
var modelIndex = UsedModels[index];
|
||||
var currentMask = model.AsHuman->Models[modelIndex]->EnabledShapeKeyIndexMask;
|
||||
var newMask = (currentMask & ~_temporaryMasks[index]) | _temporaryValues[index];
|
||||
Penumbra.Log.Excessive($"Changed Model Mask from {currentMask:X} to {newMask:X}.");
|
||||
model.AsHuman->Models[modelIndex]->EnabledShapeKeyIndexMask = newMask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_communicator.ModelAttributeComputed.Unsubscribe(OnAttributeComputed);
|
||||
}
|
||||
|
||||
private unsafe void ResetCache(Model human)
|
||||
{
|
||||
for (var i = 0; i < NumSlots; ++i)
|
||||
{
|
||||
_temporaryMasks[i] = 0;
|
||||
|
|
@ -66,17 +64,20 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
_temporaryIndices[i].Clear();
|
||||
|
||||
var modelIndex = UsedModels[i];
|
||||
var model = human.AsHuman->Models[modelIndex];
|
||||
var model = human.AsHuman->Models[modelIndex.ToIndex()];
|
||||
if (model is null || model->ModelResourceHandle is null)
|
||||
continue;
|
||||
|
||||
ref var shapes = ref model->ModelResourceHandle->Shapes;
|
||||
foreach (var (shape, index) in shapes.Where(kvp => CheckShapes(kvp.Key.AsSpan(), modelIndex)))
|
||||
foreach (var (shape, index) in shapes.Where(kvp => ShpIdentifier.ValidateCustomShapeString(kvp.Key.Value)))
|
||||
{
|
||||
if (ShapeString.TryRead(shape.Value, out var shapeString))
|
||||
{
|
||||
_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))
|
||||
_temporaryValues[i] |= (ushort)(1 << index);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -85,42 +86,32 @@ public class ShapeManager : IRequiredService, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
UpdateMasks();
|
||||
UpdateDefaultMasks();
|
||||
}
|
||||
|
||||
private static bool CheckShapes(ReadOnlySpan<byte> shape, byte index)
|
||||
=> index switch
|
||||
{
|
||||
1 => shape.StartsWith("shp_wa_"u8) || shape.StartsWith("shp_wr_"u8),
|
||||
2 => shape.StartsWith("shp_wr_"u8),
|
||||
3 => shape.StartsWith("shp_wa_"u8) || shape.StartsWith("shp_an"u8),
|
||||
4 => shape.StartsWith("shp_an"u8),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private void UpdateMasks()
|
||||
private void UpdateDefaultMasks()
|
||||
{
|
||||
foreach (var (shape, topIndex) in _temporaryIndices[0])
|
||||
foreach (var (shape, topIndex) in _temporaryIndices[1])
|
||||
{
|
||||
if (_temporaryIndices[1].TryGetValue(shape, out var handIndex))
|
||||
if (shape[4] is (byte)'w' && shape[5] is (byte)'r' && _temporaryIndices[2].TryGetValue(shape, out var handIndex))
|
||||
{
|
||||
_temporaryValues[0] |= 1u << topIndex;
|
||||
_temporaryValues[1] |= 1u << handIndex;
|
||||
_temporaryValues[1] |= 1u << topIndex;
|
||||
_temporaryValues[2] |= 1u << handIndex;
|
||||
}
|
||||
|
||||
if (_temporaryIndices[2].TryGetValue(shape, out var legIndex))
|
||||
if (shape[4] is (byte)'w' && shape[5] is (byte)'a' && _temporaryIndices[3].TryGetValue(shape, out var legIndex))
|
||||
{
|
||||
_temporaryValues[0] |= 1u << topIndex;
|
||||
_temporaryValues[2] |= 1u << legIndex;
|
||||
_temporaryValues[1] |= 1u << topIndex;
|
||||
_temporaryValues[3] |= 1u << legIndex;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (shape, bottomIndex) in _temporaryIndices[2])
|
||||
foreach (var (shape, bottomIndex) in _temporaryIndices[3])
|
||||
{
|
||||
if (_temporaryIndices[3].TryGetValue(shape, out var footIndex))
|
||||
if (shape[4] is (byte)'a' && shape[5] is (byte)'n' && _temporaryIndices[4].TryGetValue(shape, out var footIndex))
|
||||
{
|
||||
_temporaryValues[2] |= 1u << bottomIndex;
|
||||
_temporaryValues[3] |= 1u << footIndex;
|
||||
_temporaryValues[3] |= 1u << bottomIndex;
|
||||
_temporaryValues[4] |= 1u << footIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Lumina.Misc;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.Files.PhybStructs;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public struct ShapeString : IEquatable<ShapeString>
|
||||
public struct ShapeString : IEquatable<ShapeString>, IComparable<ShapeString>
|
||||
{
|
||||
public const int MaxLength = 30;
|
||||
|
||||
|
|
@ -22,6 +23,20 @@ public struct ShapeString : IEquatable<ShapeString>
|
|||
public override string ToString()
|
||||
=> Encoding.UTF8.GetString(_buffer[..Length]);
|
||||
|
||||
public byte this[int index]
|
||||
=> _buffer[index];
|
||||
|
||||
public unsafe ReadOnlySpan<byte> AsSpan
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(ptr, Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ShapeString other)
|
||||
=> Length == other.Length && _buffer[..Length].SequenceEqual(other._buffer[..Length]);
|
||||
|
||||
|
|
@ -43,6 +58,14 @@ public struct ShapeString : IEquatable<ShapeString>
|
|||
return TryRead(span, out ret);
|
||||
}
|
||||
|
||||
public unsafe int CompareTo(ShapeString other)
|
||||
{
|
||||
fixed (void* lhs = &this)
|
||||
{
|
||||
return ByteStringFunctions.Compare((byte*)lhs, Length, (byte*)&other, other.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryRead(ReadOnlySpan<byte> utf8, out ShapeString ret)
|
||||
{
|
||||
if (utf8.Length is 0 or > MaxLength)
|
||||
|
|
@ -69,6 +92,14 @@ public struct ShapeString : IEquatable<ShapeString>
|
|||
return true;
|
||||
}
|
||||
|
||||
public void ForceLength(byte length)
|
||||
{
|
||||
if (length > MaxLength)
|
||||
length = MaxLength;
|
||||
_buffer[length] = 0;
|
||||
_buffer[31] = length;
|
||||
}
|
||||
|
||||
private sealed class Converter : JsonConverter<ShapeString>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, ShapeString value, JsonSerializer serializer)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue