mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-21 07:17:53 +01:00
Introduce Identifiers and strong entry types for each meta manipulation and use them in the manipulations.
This commit is contained in:
parent
ceed8531af
commit
2e9f184454
27 changed files with 533 additions and 163 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
87
Penumbra/Meta/Manipulations/Eqdp.cs
Normal file
87
Penumbra/Meta/Manipulations/Eqdp.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
72
Penumbra/Meta/Manipulations/Eqp.cs
Normal file
72
Penumbra/Meta/Manipulations/Eqp.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
113
Penumbra/Meta/Manipulations/Est.cs
Normal file
113
Penumbra/Meta/Manipulations/Est.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
39
Penumbra/Meta/Manipulations/Gmp.cs
Normal file
39
Penumbra/Meta/Manipulations/Gmp.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
52
Penumbra/Meta/Manipulations/Rsp.cs
Normal file
52
Penumbra/Meta/Manipulations/Rsp.cs
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue