Improve meta manipulation handling a bit.

This commit is contained in:
Ottermandias 2024-06-08 14:55:10 +02:00
parent 250c4034e0
commit d7b60206d7
14 changed files with 242 additions and 66 deletions

View file

@ -160,7 +160,7 @@ public class TemporaryApi(
/// Only returns true if all conversions are successful and distinct. /// Only returns true if all conversions are successful and distinct.
/// </summary> /// </summary>
private static bool ConvertManips(string manipString, private static bool ConvertManips(string manipString,
[NotNullWhen(true)] out HashSet<MetaManipulation>? manips) [NotNullWhen(true)] out MetaDictionary? manips)
{ {
if (manipString.Length == 0) if (manipString.Length == 0)
{ {
@ -174,7 +174,7 @@ public class TemporaryApi(
return false; return false;
} }
manips = new HashSet<MetaManipulation>(manipArray!.Length); manips = new MetaDictionary(manipArray!.Length);
foreach (var manip in manipArray.Where(m => m.Validate())) foreach (var manip in manipArray.Where(m => m.Validate()))
{ {
if (manips.Add(manip)) if (manips.Add(manip))

View file

@ -43,7 +43,7 @@ public class TempModManager : IDisposable
=> _modsForAllCollections; => _modsForAllCollections;
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict, public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
HashSet<MetaManipulation> manips, ModPriority priority) MetaDictionary manips, ModPriority priority)
{ {
var mod = GetOrCreateMod(tag, collection, priority, out var created); var mod = GetOrCreateMod(tag, collection, priority, out var created);
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}."); Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");

View file

@ -8,11 +8,12 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation> public readonly struct EqdpManipulation(EqdpIdentifier identifier, EqdpEntry entry) : IMetaManipulation<EqdpManipulation>
{ {
[JsonIgnore] [JsonIgnore]
public EqdpIdentifier Identifier { get; private init; } public EqdpIdentifier Identifier { get; } = identifier;
public EqdpEntry Entry { get; private init; }
public EqdpEntry Entry { get; } = entry;
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public Gender Gender public Gender Gender
@ -31,20 +32,18 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
[JsonConstructor] [JsonConstructor]
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId) public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
{ : this(new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)), Eqdp.Mask(slot) & entry)
Identifier = new EqdpIdentifier(setId, slot, Names.CombinedRace(gender, race)); { }
Entry = Eqdp.Mask(Slot) & entry;
}
public EqdpManipulation Copy(EqdpManipulation entry) public EqdpManipulation Copy(EqdpManipulation entry)
{ {
if (entry.Slot != Slot) if (entry.Slot != Slot)
{ {
var (bit1, bit2) = entry.Entry.ToBits(entry.Slot); var (bit1, bit2) = entry.Entry.ToBits(entry.Slot);
return new EqdpManipulation(Eqdp.FromSlotAndBits(Slot, bit1, bit2), Slot, Gender, Race, SetId); return new EqdpManipulation(Identifier, Eqdp.FromSlotAndBits(Slot, bit1, bit2));
} }
return new EqdpManipulation(entry.Entry, Slot, Gender, Race, SetId); return new EqdpManipulation(Identifier, entry.Entry);
} }
public EqdpManipulation Copy(EqdpEntry entry) public EqdpManipulation Copy(EqdpEntry entry)

View file

@ -9,12 +9,13 @@ using Penumbra.Util;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation> public readonly struct EqpManipulation(EqpIdentifier identifier, EqpEntry entry) : IMetaManipulation<EqpManipulation>
{ {
[JsonConverter(typeof(ForceNumericFlagEnumConverter))] [JsonIgnore]
public EqpEntry Entry { get; private init; } public EqpIdentifier Identifier { get; } = identifier;
public EqpIdentifier Identifier { get; private init; } [JsonConverter(typeof(ForceNumericFlagEnumConverter))]
public EqpEntry Entry { get; } = entry;
public PrimaryId SetId public PrimaryId SetId
=> Identifier.SetId; => Identifier.SetId;
@ -25,13 +26,11 @@ public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
[JsonConstructor] [JsonConstructor]
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId) public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
{ : this(new EqpIdentifier(setId, slot), Eqp.Mask(slot) & entry)
Identifier = new EqpIdentifier(setId, slot); { }
Entry = Eqp.Mask(slot) & entry;
}
public EqpManipulation Copy(EqpEntry entry) public EqpManipulation Copy(EqpEntry entry)
=> new(entry, Slot, SetId); => new(Identifier, entry);
public override string ToString() public override string ToString()
=> $"Eqp - {SetId} - {Slot}"; => $"Eqp - {SetId} - {Slot}";

View file

@ -8,7 +8,7 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EstManipulation : IMetaManipulation<EstManipulation> public readonly struct EstManipulation(EstIdentifier identifier, EstEntry entry) : IMetaManipulation<EstManipulation>
{ {
public static string ToName(EstType type) public static string ToName(EstType type)
=> type switch => type switch
@ -20,8 +20,9 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
_ => "unk", _ => "unk",
}; };
public EstIdentifier Identifier { get; private init; } [JsonIgnore]
public EstEntry Entry { get; private init; } public EstIdentifier Identifier { get; } = identifier;
public EstEntry Entry { get; } = entry;
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public Gender Gender public Gender Gender
@ -41,13 +42,11 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
[JsonConstructor] [JsonConstructor]
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry) public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, EstEntry entry)
{ : this(new EstIdentifier(setId, slot, Names.CombinedRace(gender, race)), entry)
Entry = entry; { }
Identifier = new EstIdentifier(setId, slot, Names.CombinedRace(gender, race));
}
public EstManipulation Copy(EstEntry entry) public EstManipulation Copy(EstEntry entry)
=> new(Gender, Race, Slot, SetId, entry); => new(Identifier, entry);
public override string ToString() public override string ToString()

View file

@ -1,9 +1,11 @@
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation> public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation>, IMetaIdentifier
{ {
public GlobalEqpType Type { get; init; } public GlobalEqpType Type { get; init; }
public PrimaryId Condition { get; init; } public PrimaryId Condition { get; init; }
@ -19,6 +21,28 @@ public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipu
return Condition != 0; return Condition != 0;
} }
public JObject AddToJson(JObject jObj)
{
jObj[nameof(Type)] = Type.ToString();
jObj[nameof(Condition)] = Condition.Id;
return jObj;
}
public static GlobalEqpManipulation? FromJson(JObject? jObj)
{
if (jObj == null)
return null;
var type = jObj[nameof(Type)]?.ToObject<GlobalEqpType>() ?? (GlobalEqpType)100;
var condition = jObj[nameof(Condition)]?.ToObject<PrimaryId>() ?? 0;
var ret = new GlobalEqpManipulation
{
Type = type,
Condition = condition,
};
return ret.Validate() ? ret : null;
}
public bool Equals(GlobalEqpManipulation other) public bool Equals(GlobalEqpManipulation other)
=> Type == other.Type => Type == other.Type
@ -45,6 +69,9 @@ public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipu
public override string ToString() public override string ToString()
=> $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}"; => $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}";
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{ }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> (MetaIndex)(-1); => MetaIndex.Eqp;
} }

View file

@ -6,24 +6,23 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation> public readonly struct GmpManipulation(GmpIdentifier identifier, GmpEntry entry) : IMetaManipulation<GmpManipulation>
{ {
public GmpIdentifier Identifier { get; private init; } [JsonIgnore]
public GmpIdentifier Identifier { get; } = identifier;
public GmpEntry Entry { get; private init; } public GmpEntry Entry { get; } = entry;
public PrimaryId SetId public PrimaryId SetId
=> Identifier.SetId; => Identifier.SetId;
[JsonConstructor] [JsonConstructor]
public GmpManipulation(GmpEntry entry, PrimaryId setId) public GmpManipulation(GmpEntry entry, PrimaryId setId)
{ : this(new GmpIdentifier(setId), entry)
Entry = entry; { }
Identifier = new GmpIdentifier(setId);
}
public GmpManipulation Copy(GmpEntry entry) public GmpManipulation Copy(GmpEntry entry)
=> new(entry, SetId); => new(Identifier, entry);
public override string ToString() public override string ToString()
=> $"Gmp - {SetId}"; => $"Gmp - {SetId}";

View file

@ -0,0 +1,140 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Structs;
using ImcEntry = Penumbra.GameData.Structs.ImcEntry;
namespace Penumbra.Meta.Manipulations;
[JsonConverter(typeof(Converter))]
public sealed class MetaDictionary : HashSet<MetaManipulation>
{
public MetaDictionary()
{ }
public MetaDictionary(int capacity)
: base(capacity)
{ }
private class Converter : JsonConverter<MetaDictionary>
{
public override void WriteJson(JsonWriter writer, MetaDictionary? value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
return;
}
writer.WriteStartArray();
foreach (var item in value)
{
writer.WriteStartObject();
writer.WritePropertyName("Type");
writer.WriteValue(item.ManipulationType.ToString());
writer.WritePropertyName("Manipulation");
serializer.Serialize(writer, item.Manipulation);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
public override MetaDictionary ReadJson(JsonReader reader, Type objectType, MetaDictionary? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
var dict = existingValue ?? [];
dict.Clear();
var jObj = JArray.Load(reader);
foreach (var item in jObj)
{
var type = item["Type"]?.ToObject<MetaManipulation.Type>() ?? MetaManipulation.Type.Unknown;
if (type is MetaManipulation.Type.Unknown)
{
Penumbra.Log.Warning($"Invalid Meta Manipulation Type {type} encountered.");
continue;
}
if (item["Manipulation"] is not JObject manip)
{
Penumbra.Log.Warning($"Manipulation of type {type} does not contain manipulation data.");
continue;
}
switch (type)
{
case MetaManipulation.Type.Imc:
{
var identifier = ImcIdentifier.FromJson(manip);
var entry = manip["Entry"]?.ToObject<ImcEntry>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new ImcManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid IMC Manipulation encountered.");
break;
}
case MetaManipulation.Type.Eqdp:
{
var identifier = EqdpIdentifier.FromJson(manip);
var entry = (EqdpEntry?)manip["Entry"]?.ToObject<ushort>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new EqdpManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid EQDP Manipulation encountered.");
break;
}
case MetaManipulation.Type.Eqp:
{
var identifier = EqpIdentifier.FromJson(manip);
var entry = (EqpEntry?)manip["Entry"]?.ToObject<ulong>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new EqpManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid EQP Manipulation encountered.");
break;
}
case MetaManipulation.Type.Est:
{
var identifier = EstIdentifier.FromJson(manip);
var entry = manip["Entry"]?.ToObject<EstEntry>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new EstManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid EST Manipulation encountered.");
break;
}
case MetaManipulation.Type.Gmp:
{
var identifier = GmpIdentifier.FromJson(manip);
var entry = manip["Entry"]?.ToObject<GmpEntry>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new GmpManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid GMP Manipulation encountered.");
break;
}
case MetaManipulation.Type.Rsp:
{
var identifier = RspIdentifier.FromJson(manip);
var entry = manip["Entry"]?.ToObject<RspEntry>();
if (identifier.HasValue && entry.HasValue)
dict.Add(new MetaManipulation(new RspManipulation(identifier.Value, entry.Value)));
else
Penumbra.Log.Warning("Invalid RSP Manipulation encountered.");
break;
}
case MetaManipulation.Type.GlobalEqp:
{
var identifier = GlobalEqpManipulation.FromJson(manip);
if (identifier.HasValue)
dict.Add(new MetaManipulation(identifier.Value));
else
Penumbra.Log.Warning("Invalid Global EQP Manipulation encountered.");
break;
}
}
}
return dict;
}
}
}

View file

@ -1,3 +1,4 @@
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
@ -12,13 +13,31 @@ public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attrib
=> changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null); => changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null);
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> throw new NotImplementedException(); => MetaIndex.HumanCmp;
public bool Validate() public bool Validate()
=> throw new NotImplementedException(); => SubRace is not SubRace.Unknown
&& Enum.IsDefined(SubRace)
&& Attribute is not RspAttribute.NumAttributes
&& Enum.IsDefined(Attribute);
public JObject AddToJson(JObject jObj) public JObject AddToJson(JObject jObj)
=> throw new NotImplementedException(); {
jObj["SubRace"] = SubRace.ToString();
jObj["Attribute"] = Attribute.ToString();
return jObj;
}
public static RspIdentifier? FromJson(JObject? jObj)
{
if (jObj == null)
return null;
var subRace = jObj["SubRace"]?.ToObject<SubRace>() ?? SubRace.Unknown;
var attribute = jObj["Attribute"]?.ToObject<RspAttribute>() ?? RspAttribute.NumAttributes;
var ret = new RspIdentifier(subRace, attribute);
return ret.Validate() ? ret : null;
}
} }
[JsonConverter(typeof(Converter))] [JsonConverter(typeof(Converter))]
@ -28,6 +47,9 @@ public readonly record struct RspEntry(float Value) : IComparisonOperators<RspEn
public const float MaxValue = 512f; public const float MaxValue = 512f;
public static readonly RspEntry One = new(1f); public static readonly RspEntry One = new(1f);
public bool Validate()
=> Value is >= MinValue and <= MaxValue;
private class Converter : JsonConverter<RspEntry> private class Converter : JsonConverter<RspEntry>
{ {
public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer)

View file

@ -7,10 +7,12 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct RspManipulation : IMetaManipulation<RspManipulation> public readonly struct RspManipulation(RspIdentifier identifier, RspEntry entry) : IMetaManipulation<RspManipulation>
{ {
public RspIdentifier Identifier { get; private init; } [JsonIgnore]
public RspEntry Entry { get; private init; } public RspIdentifier Identifier { get; } = identifier;
public RspEntry Entry { get; } = entry;
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public SubRace SubRace public SubRace SubRace
@ -22,13 +24,11 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
[JsonConstructor] [JsonConstructor]
public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry) public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry)
{ : this(new RspIdentifier(subRace, attribute), entry)
Entry = entry; { }
Identifier = new RspIdentifier(subRace, attribute);
}
public RspManipulation Copy(RspEntry entry) public RspManipulation Copy(RspEntry entry)
=> new(SubRace, Attribute, entry); => new(Identifier, entry);
public override string ToString() public override string ToString()
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}"; => $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
@ -63,14 +63,5 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
} }
public bool Validate() public bool Validate()
{ => Identifier.Validate() && Entry.Validate();
if (SubRace is SubRace.Unknown || !Enum.IsDefined(SubRace))
return false;
if (!Enum.IsDefined(Attribute))
return false;
if (Entry.Value is < RspEntry.MinValue or > RspEntry.MaxValue)
return false;
return true;
}
} }

View file

@ -13,7 +13,7 @@ public class DefaultSubMod(IMod mod) : IModDataContainer
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
public HashSet<MetaManipulation> Manipulations { get; set; } = []; public MetaDictionary Manipulations { get; set; } = [];
IMod IModDataContainer.Mod IMod IModDataContainer.Mod
=> Mod; => Mod;

View file

@ -10,9 +10,9 @@ public interface IModDataContainer
public IMod Mod { get; } public IMod Mod { get; }
public IModGroup? Group { get; } public IModGroup? Group { get; }
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
public HashSet<MetaManipulation> Manipulations { get; set; } public MetaDictionary Manipulations { get; set; }
public string GetName(); public string GetName();
public string GetFullName(); public string GetFullName();

View file

@ -33,7 +33,7 @@ public abstract class OptionSubMod(IModGroup group) : IModOption, IModDataContai
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = []; public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
public HashSet<MetaManipulation> Manipulations { get; set; } = []; public MetaDictionary Manipulations { get; set; } = [];
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations) public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
=> SubMod.AddContainerTo(this, redirections, manipulations); => SubMod.AddContainerTo(this, redirections, manipulations);

View file

@ -57,7 +57,7 @@ public class TemporaryMod : IMod
public bool SetManipulation(MetaManipulation manip) public bool SetManipulation(MetaManipulation manip)
=> Default.Manipulations.Remove(manip) | Default.Manipulations.Add(manip); => Default.Manipulations.Remove(manip) | Default.Manipulations.Add(manip);
public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, HashSet<MetaManipulation> manips) public void SetAll(Dictionary<Utf8GamePath, FullPath> dict, MetaDictionary manips)
{ {
Default.Files = dict; Default.Files = dict;
Default.Manipulations = manips; Default.Manipulations = manips;