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

View file

@ -43,7 +43,7 @@ public class TempModManager : IDisposable
=> _modsForAllCollections;
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);
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");

View file

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

View file

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

View file

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

View file

@ -1,9 +1,11 @@
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations;
public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation>
public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipulation>, IMetaIdentifier
{
public GlobalEqpType Type { get; init; }
public PrimaryId Condition { get; init; }
@ -19,6 +21,28 @@ public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipu
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)
=> Type == other.Type
@ -45,6 +69,9 @@ public readonly struct GlobalEqpManipulation : IMetaManipulation<GlobalEqpManipu
public override string ToString()
=> $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}";
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{ }
public MetaIndex FileIndex()
=> (MetaIndex)(-1);
=> MetaIndex.Eqp;
}

View file

@ -6,24 +6,23 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations;
[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
=> Identifier.SetId;
[JsonConstructor]
public GmpManipulation(GmpEntry entry, PrimaryId setId)
{
Entry = entry;
Identifier = new GmpIdentifier(setId);
}
: this(new GmpIdentifier(setId), entry)
{ }
public GmpManipulation Copy(GmpEntry entry)
=> new(entry, SetId);
=> new(Identifier, entry);
public override string ToString()
=> $"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.Linq;
using Penumbra.GameData.Data;
@ -12,13 +13,31 @@ public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attrib
=> changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null);
public MetaIndex FileIndex()
=> throw new NotImplementedException();
=> MetaIndex.HumanCmp;
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)
=> 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))]
@ -28,6 +47,9 @@ public readonly record struct RspEntry(float Value) : IComparisonOperators<RspEn
public const float MaxValue = 512f;
public static readonly RspEntry One = new(1f);
public bool Validate()
=> Value is >= MinValue and <= MaxValue;
private class Converter : JsonConverter<RspEntry>
{
public override void WriteJson(JsonWriter writer, RspEntry value, JsonSerializer serializer)

View file

@ -7,10 +7,12 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations;
[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; }
public RspEntry Entry { get; private init; }
[JsonIgnore]
public RspIdentifier Identifier { get; } = identifier;
public RspEntry Entry { get; } = entry;
[JsonConverter(typeof(StringEnumConverter))]
public SubRace SubRace
@ -22,13 +24,11 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
[JsonConstructor]
public RspManipulation(SubRace subRace, RspAttribute attribute, RspEntry entry)
{
Entry = entry;
Identifier = new RspIdentifier(subRace, attribute);
}
: this(new RspIdentifier(subRace, attribute), entry)
{ }
public RspManipulation Copy(RspEntry entry)
=> new(SubRace, Attribute, entry);
=> new(Identifier, entry);
public override string ToString()
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
@ -63,14 +63,5 @@ public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
}
public bool 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;
}
=> Identifier.Validate() && Entry.Validate();
}

View file

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

View file

@ -10,9 +10,9 @@ public interface IModDataContainer
public IMod Mod { get; }
public IModGroup? Group { get; }
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
public HashSet<MetaManipulation> Manipulations { get; set; }
public Dictionary<Utf8GamePath, FullPath> Files { get; set; }
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; }
public MetaDictionary Manipulations { get; set; }
public string GetName();
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> FileSwaps { get; set; } = [];
public HashSet<MetaManipulation> Manipulations { get; set; } = [];
public MetaDictionary Manipulations { get; set; } = [];
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
=> SubMod.AddContainerTo(this, redirections, manipulations);

View file

@ -57,7 +57,7 @@ public class TemporaryMod : IMod
public bool SetManipulation(MetaManipulation 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.Manipulations = manips;