Introduce Identifiers and strong entry types for each meta manipulation and use them in the manipulations.

This commit is contained in:
Ottermandias 2024-06-06 17:26:25 +02:00
parent ceed8531af
commit 2e9f184454
27 changed files with 533 additions and 163 deletions

View file

@ -2,6 +2,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
using Penumbra.Interop.Services;
using Penumbra.Meta.Manipulations;
using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@ -17,10 +18,10 @@ public sealed unsafe class CmpFile : MetaBaseFile
private const int RacialScalingStart = 0x2A800;
public float this[SubRace subRace, RspAttribute attribute]
public RspEntry this[SubRace subRace, RspAttribute attribute]
{
get => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
set => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4) = value;
get => *(RspEntry*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
set => *(RspEntry*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4) = value;
}
public override void Reset()
@ -39,10 +40,10 @@ public sealed unsafe class CmpFile : MetaBaseFile
Reset();
}
public static float GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
public static RspEntry GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
{
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
return *(RspEntry*)(data + RacialScalingStart + ToRspIndex(subRace) * RspData.ByteSize + (int)attribute * 4);
}
private static int ToRspIndex(SubRace subRace)

View file

@ -34,26 +34,26 @@ public sealed unsafe class EstFile : MetaBaseFile
Removed,
}
public ushort this[GenderRace genderRace, ushort setId]
public EstEntry this[GenderRace genderRace, PrimaryId setId]
{
get
{
var (idx, exists) = FindEntry(genderRace, setId);
if (!exists)
return 0;
return EstEntry.Zero;
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
return *(EstEntry*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
}
set => SetEntry(genderRace, setId, value);
}
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
private void InsertEntry(int idx, GenderRace genderRace, PrimaryId setId, EstEntry skeletonId)
{
if (Length < Size + EntryDescSize + EntrySize)
ResizeResources(Length + IncreaseSize);
var control = (Info*)(Data + 4);
var entries = (ushort*)(control + Count);
var entries = (EstEntry*)(control + Count);
for (var i = Count - 1; i >= idx; --i)
entries[i + 3] = entries[i];
@ -94,10 +94,10 @@ public sealed unsafe class EstFile : MetaBaseFile
[StructLayout(LayoutKind.Sequential, Size = 4)]
private struct Info : IComparable<Info>
{
public readonly ushort SetId;
public readonly PrimaryId SetId;
public readonly GenderRace GenderRace;
public Info(GenderRace gr, ushort setId)
public Info(GenderRace gr, PrimaryId setId)
{
GenderRace = gr;
SetId = setId;
@ -106,42 +106,42 @@ public sealed unsafe class EstFile : MetaBaseFile
public int CompareTo(Info other)
{
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo(other.SetId);
return genderRaceComparison != 0 ? genderRaceComparison : SetId.Id.CompareTo(other.SetId.Id);
}
}
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, ushort setId)
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, PrimaryId setId)
{
var idx = data.BinarySearch(new Info(genderRace, setId));
return idx < 0 ? (~idx, false) : (idx, true);
}
private (int, bool) FindEntry(GenderRace genderRace, ushort setId)
private (int, bool) FindEntry(GenderRace genderRace, PrimaryId setId)
{
var span = new ReadOnlySpan<Info>(Data + 4, Count);
return FindEntry(span, genderRace, setId);
}
public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId)
public EstEntryChange SetEntry(GenderRace genderRace, PrimaryId setId, EstEntry skeletonId)
{
var (idx, exists) = FindEntry(genderRace, setId);
if (exists)
{
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
var value = *(EstEntry*)(Data + 4 * (Count + 1) + 2 * idx);
if (value == skeletonId)
return EstEntryChange.Unchanged;
if (skeletonId == 0)
if (skeletonId == EstEntry.Zero)
{
RemoveEntry(idx);
return EstEntryChange.Removed;
}
*(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
*(EstEntry*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
return EstEntryChange.Changed;
}
if (skeletonId == 0)
if (skeletonId == EstEntry.Zero)
return EstEntryChange.Unchanged;
InsertEntry(idx, genderRace, setId, skeletonId);
@ -156,7 +156,7 @@ public sealed unsafe class EstFile : MetaBaseFile
MemoryUtility.MemSet(Data + length, 0, Length - length);
}
public EstFile(MetaFileManager manager, EstManipulation.EstType estType)
public EstFile(MetaFileManager manager, EstType estType)
: base(manager, (MetaIndex)estType)
{
var length = DefaultData.Length;
@ -164,24 +164,24 @@ public sealed unsafe class EstFile : MetaBaseFile
Reset();
}
public ushort GetDefault(GenderRace genderRace, ushort setId)
public EstEntry GetDefault(GenderRace genderRace, PrimaryId setId)
=> GetDefault(Manager, Index, genderRace, setId);
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, PrimaryId primaryId)
public static EstEntry 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, primaryId.Id);
if (!found)
return 0;
return EstEntry.Zero;
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
return *(EstEntry*)(data + 4 + count * EntryDescSize + idx * EntrySize);
}
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
public static EstEntry 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, PrimaryId primaryId)
public static EstEntry GetDefault(MetaFileManager manager, EstType estType, GenderRace genderRace, PrimaryId primaryId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
}

View file

@ -0,0 +1,87 @@
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 EqdpIdentifier(PrimaryId SetId, EquipSlot Slot, GenderRace GenderRace)
: IMetaIdentifier, IComparable<EqdpIdentifier>
{
public ModelRace Race
=> GenderRace.Split().Item2;
public Gender Gender
=> GenderRace.Split().Item1;
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, Slot));
public MetaIndex FileIndex()
=> CharacterUtilityData.EqdpIdx(GenderRace, Slot.IsAccessory());
public override string ToString()
=> $"Eqdp - {SetId} - {Slot.ToName()} - {GenderRace.ToName()}";
public bool Validate()
{
var mask = Eqdp.Mask(Slot);
if (mask == 0)
return false;
if (FileIndex() == (MetaIndex)(-1))
return false;
// No check for set id.
return true;
}
public int CompareTo(EqdpIdentifier other)
{
var gr = GenderRace.CompareTo(other.GenderRace);
if (gr != 0)
return gr;
var set = SetId.Id.CompareTo(other.SetId.Id);
if (set != 0)
return set;
return Slot.CompareTo(other.Slot);
}
public static EqdpIdentifier? FromJson(JObject jObj)
{
var gender = jObj["Gender"]?.ToObject<Gender>() ?? Gender.Unknown;
var race = jObj["Race"]?.ToObject<ModelRace>() ?? ModelRace.Unknown;
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
var slot = jObj["Slot"]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown;
var ret = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race));
return ret.Validate() ? ret : null;
}
public JObject AddToJson(JObject jObj)
{
var (gender, race) = GenderRace.Split();
jObj["Gender"] = gender.ToString();
jObj["Race"] = race.ToString();
jObj["SetId"] = SetId.Id.ToString();
jObj["Slot"] = Slot.ToString();
return jObj;
}
}
public readonly record struct InternalEqdpEntry(bool Model, bool Material)
{
private InternalEqdpEntry((bool, bool) val)
: this(val.Item1, val.Item2)
{ }
public InternalEqdpEntry(EqdpEntry entry, EquipSlot slot)
: this(entry.ToBits(slot))
{ }
public EqdpEntry ToEntry(EquipSlot slot)
=> Eqdp.FromSlotAndBits(slot, Model, Material);
}

View file

@ -10,27 +10,30 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
{
public EqdpEntry Entry { get; private init; }
[JsonIgnore]
public EqdpIdentifier Identifier { get; private init; }
public EqdpEntry Entry { get; private init; }
[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; private init; }
public Gender Gender
=> Identifier.Gender;
[JsonConverter(typeof(StringEnumConverter))]
public ModelRace Race { get; private init; }
public ModelRace Race
=> Identifier.Race;
public PrimaryId SetId { get; private init; }
public PrimaryId SetId
=> Identifier.SetId;
[JsonConverter(typeof(StringEnumConverter))]
public EquipSlot Slot { get; private init; }
public EquipSlot Slot
=> Identifier.Slot;
[JsonConstructor]
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
{
Gender = gender;
Race = race;
SetId = setId;
Slot = slot;
Entry = Eqdp.Mask(Slot) & entry;
Identifier = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race));
Entry = Eqdp.Mask(Slot) & entry;
}
public EqdpManipulation Copy(EqdpManipulation entry)

View file

@ -0,0 +1,72 @@
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 EqpIdentifier(PrimaryId SetId, EquipSlot Slot) : IMetaIdentifier, IComparable<EqpIdentifier>
{
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, Slot));
public MetaIndex FileIndex()
=> MetaIndex.Eqp;
public override string ToString()
=> $"Eqp - {SetId} - {Slot}";
public bool Validate()
{
var mask = Eqp.Mask(Slot);
if (mask == 0)
return false;
// No check for set id.
return true;
}
public int CompareTo(EqpIdentifier other)
{
var set = SetId.Id.CompareTo(other.SetId.Id);
if (set != 0)
return set;
return Slot.CompareTo(other.Slot);
}
public static EqpIdentifier? FromJson(JObject jObj)
{
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
var slot = jObj["Slot"]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown;
var ret = new EqpIdentifier(setId, slot);
return ret.Validate() ? ret : null;
}
public JObject AddToJson(JObject jObj)
{
jObj["SetId"] = SetId.Id.ToString();
jObj["Slot"] = Slot.ToString();
return jObj;
}
}
public readonly record struct EqpEntryInternal(uint Value)
{
public EqpEntryInternal(EqpEntry entry, EquipSlot slot)
: this(GetValue(entry, slot))
{ }
public EqpEntry ToEntry(EquipSlot slot)
{
var (offset, mask) = Eqp.OffsetAndMask(slot);
return (EqpEntry)((ulong)Value << offset) & mask;
}
private static uint GetValue(EqpEntry entry, EquipSlot slot)
{
var (offset, mask) = Eqp.OffsetAndMask(slot);
return (uint)((ulong)(entry & mask) >> offset);
}
}

View file

@ -5,7 +5,6 @@ using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Util;
using SharpCompress.Common;
namespace Penumbra.Meta.Manipulations;
@ -15,17 +14,20 @@ public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
public EqpEntry Entry { get; private init; }
public PrimaryId SetId { get; private init; }
public EqpIdentifier Identifier { get; private init; }
public PrimaryId SetId
=> Identifier.SetId;
[JsonConverter(typeof(StringEnumConverter))]
public EquipSlot Slot { get; private init; }
public EquipSlot Slot
=> Identifier.Slot;
[JsonConstructor]
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
{
Slot = slot;
SetId = setId;
Entry = Eqp.Mask(slot) & entry;
Identifier = new EqpIdentifier(setId, slot);
Entry = Eqp.Mask(slot) & entry;
}
public EqpManipulation Copy(EqpEntry entry)

View file

@ -0,0 +1,113 @@
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 enum EstType : byte
{
Hair = MetaIndex.HairEst,
Face = MetaIndex.FaceEst,
Body = MetaIndex.BodyEst,
Head = MetaIndex.HeadEst,
}
public readonly record struct EstIdentifier(PrimaryId SetId, EstType Slot, GenderRace GenderRace)
: IMetaIdentifier, IComparable<EstIdentifier>
{
public ModelRace Race
=> GenderRace.Split().Item2;
public Gender Gender
=> GenderRace.Split().Item1;
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{
switch (Slot)
{
case EstType.Hair:
changedItems.TryAdd(
$"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Hair (Hair) {SetId}", null);
break;
case EstType.Face:
changedItems.TryAdd(
$"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Face (Face) {SetId}", null);
break;
case EstType.Body:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Body));
break;
case EstType.Head:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Head));
break;
}
}
public MetaIndex FileIndex()
=> (MetaIndex)Slot;
public override string ToString()
=> $"Est - {SetId} - {Slot} - {GenderRace.ToName()}";
public bool Validate()
{
if (!Enum.IsDefined(Slot))
return false;
if (GenderRace is GenderRace.Unknown || !Enum.IsDefined(GenderRace))
return false;
// No known check for set id.
return true;
}
public int CompareTo(EstIdentifier other)
{
var gr = GenderRace.CompareTo(other.GenderRace);
if (gr != 0)
return gr;
var id = SetId.Id.CompareTo(other.SetId.Id);
return id != 0 ? id : Slot.CompareTo(other.Slot);
}
public static EstIdentifier? FromJson(JObject jObj)
{
var gender = jObj["Gender"]?.ToObject<Gender>() ?? Gender.Unknown;
var race = jObj["Race"]?.ToObject<ModelRace>() ?? ModelRace.Unknown;
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
var slot = jObj["Slot"]?.ToObject<EstType>() ?? 0;
var ret = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race));
return ret.Validate() ? ret : null;
}
public JObject AddToJson(JObject jObj)
{
var (gender, race) = GenderRace.Split();
jObj["Gender"] = gender.ToString();
jObj["Race"] = race.ToString();
jObj["SetId"] = SetId.Id.ToString();
jObj["Slot"] = Slot.ToString();
return jObj;
}
}
[JsonConverter(typeof(Converter))]
public readonly record struct EstEntry(ushort Value)
{
public static readonly EstEntry Zero = new(0);
public PrimaryId AsId
=> new(Value);
private class Converter : JsonConverter<EstEntry>
{
public override void WriteJson(JsonWriter writer, EstEntry value, JsonSerializer serializer)
=> serializer.Serialize(writer, value.Value);
public override EstEntry ReadJson(JsonReader reader, Type objectType, EstEntry existingValue, bool hasExistingValue,
JsonSerializer serializer)
=> new(serializer.Deserialize<ushort>(reader));
}
}

View file

@ -10,14 +10,6 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
{
public enum EstType : byte
{
Hair = MetaIndex.HairEst,
Face = MetaIndex.FaceEst,
Body = MetaIndex.BodyEst,
Head = MetaIndex.HeadEst,
}
public static string ToName(EstType type)
=> type switch
{
@ -28,31 +20,33 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
_ => "unk",
};
public ushort Entry { get; private init; } // SkeletonIdx.
public EstIdentifier Identifier { get; private init; }
public EstEntry Entry { get; private init; }
[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; private init; }
public Gender Gender
=> Identifier.Gender;
[JsonConverter(typeof(StringEnumConverter))]
public ModelRace Race { get; private init; }
public ModelRace Race
=> Identifier.Race;
public PrimaryId SetId { get; private init; }
public PrimaryId SetId
=> Identifier.SetId;
[JsonConverter(typeof(StringEnumConverter))]
public EstType Slot { get; private init; }
public EstType Slot
=> Identifier.Slot;
[JsonConstructor]
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, ushort entry)
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry)
{
Entry = entry;
Gender = gender;
Race = race;
SetId = setId;
Slot = slot;
Entry = entry;
Identifier = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race));
}
public EstManipulation Copy(ushort entry)
public EstManipulation Copy(EstEntry entry)
=> new(Gender, Race, Slot, SetId, entry);
@ -111,3 +105,5 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
return true;
}
}

View file

@ -0,0 +1,39 @@
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 GmpIdentifier(PrimaryId SetId) : IMetaIdentifier, IComparable<GmpIdentifier>
{
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, EquipSlot.Head));
public MetaIndex FileIndex()
=> MetaIndex.Gmp;
public override string ToString()
=> $"Gmp - {SetId}";
public bool Validate()
// No known conditions.
=> true;
public int CompareTo(GmpIdentifier other)
=> SetId.Id.CompareTo(other.SetId.Id);
public static GmpIdentifier? FromJson(JObject jObj)
{
var setId = new PrimaryId(jObj["SetId"]?.ToObject<ushort>() ?? 0);
var ret = new GmpIdentifier(setId);
return ret.Validate() ? ret : null;
}
public JObject AddToJson(JObject jObj)
{
jObj["SetId"] = SetId.Id.ToString();
return jObj;
}
}

View file

@ -8,14 +8,18 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
{
public GmpEntry Entry { get; private init; }
public PrimaryId SetId { get; private init; }
public GmpIdentifier Identifier { get; private init; }
public GmpEntry Entry { get; private init; }
public PrimaryId SetId
=> Identifier.SetId;
[JsonConstructor]
public GmpManipulation(GmpEntry entry, PrimaryId setId)
{
Entry = entry;
SetId = setId;
Entry = entry;
Identifier = new GmpIdentifier(setId);
}
public GmpManipulation Copy(GmpEntry entry)

View file

@ -0,0 +1,52 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations;
public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attribute) : IMetaIdentifier
{
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null);
public MetaIndex FileIndex()
=> throw new NotImplementedException();
public bool Validate()
=> throw new NotImplementedException();
public JObject AddToJson(JObject jObj)
=> throw new NotImplementedException();
}
[JsonConverter(typeof(Converter))]
public readonly record struct RspEntry(float Value) : IComparisonOperators<RspEntry, RspEntry, bool>
{
public const float MinValue = 0.01f;
public const float MaxValue = 512f;
public static readonly RspEntry One = new(1f);
private class Converter : JsonConverter<RspEntry>
{
public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer)
=> serializer.Serialize(writer, value.Value);
public override RspEntry ReadJson(JsonReader reader, Type objectType, RspEntry existingValue, bool hasExistingValue,
JsonSerializer serializer)
=> new(serializer.Deserialize<float>(reader));
}
public static bool operator >(RspEntry left, RspEntry right)
=> left.Value > right.Value;
public static bool operator >=(RspEntry left, RspEntry right)
=> left.Value >= right.Value;
public static bool operator <(RspEntry left, RspEntry right)
=> left.Value < right.Value;
public static bool operator <=(RspEntry left, RspEntry right)
=> left.Value <= right.Value;
}

View file

@ -9,25 +9,25 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
{
public const float MinValue = 0.01f;
public const float MaxValue = 512f;
public float Entry { get; private init; }
public RspIdentifier Identifier { get; private init; }
public RspEntry Entry { get; private init; }
[JsonConverter(typeof(StringEnumConverter))]
public SubRace SubRace { get; private init; }
public SubRace SubRace
=> Identifier.SubRace;
[JsonConverter(typeof(StringEnumConverter))]
public RspAttribute Attribute { get; private init; }
public RspAttribute Attribute
=> Identifier.Attribute;
[JsonConstructor]
public RspManipulation(SubRace subRace, RspAttribute attribute, float entry)
public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry)
{
Entry = entry;
SubRace = subRace;
Attribute = attribute;
Entry = entry;
Identifier = new RspIdentifier(subRace, attribute);
}
public RspManipulation Copy(float entry)
public RspManipulation Copy(RspEntry entry)
=> new(SubRace, Attribute, entry);
public override string ToString()
@ -68,7 +68,7 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
return false;
if (!Enum.IsDefined(Attribute))
return false;
if (Entry is < MinValue or > MaxValue)
if (Entry.Value is < RspEntry.MinValue or > RspEntry.MaxValue)
return false;
return true;