Make IMC handling even better.

This commit is contained in:
Ottermandias 2024-05-24 16:15:04 +02:00
parent 65627b5002
commit 4743acf767
15 changed files with 437 additions and 459 deletions

View file

@ -0,0 +1,36 @@
using Penumbra.GameData.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta;
public class ImcChecker(MetaFileManager metaFileManager)
{
public readonly record struct CachedEntry(ImcEntry Entry, bool FileExists, bool VariantExists);
private readonly Dictionary<ImcIdentifier, CachedEntry> _cachedDefaultEntries = new();
public CachedEntry GetDefaultEntry(ImcIdentifier identifier, bool storeCache)
{
if (_cachedDefaultEntries.TryGetValue(identifier, out var entry))
return entry;
try
{
var e = ImcFile.GetDefault(metaFileManager, identifier.GamePath(), identifier.EquipSlot, identifier.Variant, out var entryExists);
entry = new CachedEntry(e, true, entryExists);
}
catch (Exception)
{
entry = new CachedEntry(default, false, false);
}
if (storeCache)
_cachedDefaultEntries.Add(identifier, entry);
return entry;
}
public CachedEntry GetDefaultEntry(ImcManipulation imcManip, bool storeCache)
=> GetDefaultEntry(new ImcIdentifier(imcManip.PrimaryId, imcManip.Variant, imcManip.ObjectType, imcManip.SecondaryId.Id,
imcManip.EquipSlot, imcManip.BodySlot), storeCache);
}

View file

@ -15,6 +15,8 @@ public readonly record struct ImcIdentifier(
EquipSlot EquipSlot,
BodySlot BodySlot) : IMetaIdentifier, IComparable<ImcIdentifier>
{
public static readonly ImcIdentifier Default = new(EquipSlot.Body, 1, (Variant)1);
public ImcIdentifier(EquipSlot slot, PrimaryId primaryId, ushort variant)
: this(primaryId, (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue),
slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot,
@ -25,6 +27,9 @@ public readonly record struct ImcIdentifier(
: this(primaryId, variant, slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot, BodySlot.Unknown)
{ }
public ImcManipulation ToManipulation(ImcEntry entry)
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId.Id, Variant.Id, EquipSlot, entry);
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{
var path = ObjectType switch
@ -137,9 +142,12 @@ public readonly record struct ImcIdentifier(
return b != 0 ? b : Variant.Id.CompareTo(other.Variant.Id);
}
public static ImcIdentifier? FromJson(JObject jObj)
public static ImcIdentifier? FromJson(JObject? jObj)
{
var objectType = jObj["PrimaryId"]?.ToObject<ObjectType>() ?? ObjectType.Unknown;
if (jObj == null)
return null;
var objectType = jObj["ObjectType"]?.ToObject<ObjectType>() ?? ObjectType.Unknown;
var primaryId = new PrimaryId(jObj["PrimaryId"]?.ToObject<ushort>() ?? 0);
var variant = jObj["Variant"]?.ToObject<ushort>() ?? 0;
if (variant > byte.MaxValue)
@ -178,12 +186,12 @@ public readonly record struct ImcIdentifier(
public JObject AddToJson(JObject jObj)
{
jObj["ObjectType"] = ObjectType.ToString();
jObj["PrimaryId"] = PrimaryId.Id;
jObj["PrimaryId"] = SecondaryId.Id;
jObj["Variant"] = Variant.Id;
jObj["EquipSlot"] = EquipSlot.ToString();
jObj["BodySlot"] = BodySlot.ToString();
jObj["ObjectType"] = ObjectType.ToString();
jObj["PrimaryId"] = PrimaryId.Id;
jObj["SecondaryId"] = SecondaryId.Id;
jObj["Variant"] = Variant.Id;
jObj["EquipSlot"] = EquipSlot.ToString();
jObj["BodySlot"] = BodySlot.ToString();
return jObj;
}
}

View file

@ -12,171 +12,96 @@ namespace Penumbra.Meta.Manipulations;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
{
public ImcEntry Entry { get; private init; }
public PrimaryId PrimaryId { get; private init; }
public PrimaryId SecondaryId { get; private init; }
public Variant Variant { get; private init; }
[JsonIgnore]
public ImcIdentifier Identifier { get; private init; }
public ImcEntry Entry { get; private init; }
public PrimaryId PrimaryId
=> Identifier.PrimaryId;
public SecondaryId SecondaryId
=> Identifier.SecondaryId;
public Variant Variant
=> Identifier.Variant;
[JsonConverter(typeof(StringEnumConverter))]
public ObjectType ObjectType { get; private init; }
public ObjectType ObjectType
=> Identifier.ObjectType;
[JsonConverter(typeof(StringEnumConverter))]
public EquipSlot EquipSlot { get; private init; }
public EquipSlot EquipSlot
=> Identifier.EquipSlot;
[JsonConverter(typeof(StringEnumConverter))]
public BodySlot BodySlot { get; private init; }
public BodySlot BodySlot
=> Identifier.BodySlot;
public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry)
: this(new ImcIdentifier(equipSlot, primaryId, variant), entry)
{ }
public ImcManipulation(ImcIdentifier identifier, ImcEntry entry)
{
Entry = entry;
PrimaryId = primaryId;
Variant = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
SecondaryId = 0;
ObjectType = equipSlot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment;
EquipSlot = equipSlot;
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
Identifier = identifier;
Entry = entry;
}
// Variants were initially ushorts but got shortened to bytes.
// There are still some manipulations around that have values > 255 for variant,
// so we change the unused value to something nonsensical in that case, just so they do not compare equal,
// and clamp the variant to 255.
[JsonConstructor]
internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, PrimaryId secondaryId, ushort variant,
internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, SecondaryId secondaryId, ushort variant,
EquipSlot equipSlot, ImcEntry entry)
{
Entry = entry;
ObjectType = objectType;
PrimaryId = primaryId;
Variant = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
if (objectType is ObjectType.Accessory or ObjectType.Equipment)
Entry = entry;
var v = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
Identifier = objectType switch
{
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
SecondaryId = 0;
EquipSlot = equipSlot;
}
else if (objectType is ObjectType.DemiHuman)
{
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
SecondaryId = secondaryId;
EquipSlot = equipSlot == EquipSlot.Unknown ? EquipSlot.Head : equipSlot;
}
else
{
BodySlot = bodySlot;
SecondaryId = secondaryId;
EquipSlot = variant > byte.MaxValue ? EquipSlot.All : EquipSlot.Unknown;
}
ObjectType.Accessory or ObjectType.Equipment => new ImcIdentifier(primaryId, v, objectType, 0, equipSlot,
variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
ObjectType.DemiHuman => new ImcIdentifier(primaryId, v, objectType, secondaryId,
equipSlot == EquipSlot.Unknown ? EquipSlot.Head : equipSlot, variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
_ => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot == EquipSlot.Unknown ? EquipSlot.Head : equipSlot,
bodySlot),
};
}
public ImcManipulation Copy(ImcEntry entry)
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant.Id, EquipSlot, entry);
=> new(Identifier, entry);
public override string ToString()
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
: $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}";
=> Identifier.ToString();
public bool Equals(ImcManipulation other)
=> PrimaryId == other.PrimaryId
&& Variant == other.Variant
&& SecondaryId == other.SecondaryId
&& ObjectType == other.ObjectType
&& EquipSlot == other.EquipSlot
&& BodySlot == other.BodySlot;
=> Identifier == other.Identifier;
public override bool Equals(object? obj)
=> obj is ImcManipulation other && Equals(other);
public override int GetHashCode()
=> HashCode.Combine(PrimaryId, Variant, SecondaryId, (int)ObjectType, (int)EquipSlot, (int)BodySlot);
=> Identifier.GetHashCode();
public int CompareTo(ImcManipulation other)
{
var o = ObjectType.CompareTo(other.ObjectType);
if (o != 0)
return o;
var i = PrimaryId.Id.CompareTo(other.PrimaryId.Id);
if (i != 0)
return i;
if (ObjectType is ObjectType.Equipment or ObjectType.Accessory)
{
var e = EquipSlot.CompareTo(other.EquipSlot);
return e != 0 ? e : Variant.Id.CompareTo(other.Variant.Id);
}
if (ObjectType is ObjectType.DemiHuman)
{
var e = EquipSlot.CompareTo(other.EquipSlot);
if (e != 0)
return e;
}
var s = SecondaryId.Id.CompareTo(other.SecondaryId.Id);
if (s != 0)
return s;
var b = BodySlot.CompareTo(other.BodySlot);
return b != 0 ? b : Variant.Id.CompareTo(other.Variant.Id);
}
=> Identifier.CompareTo(other.Identifier);
public MetaIndex FileIndex()
=> (MetaIndex)(-1);
=> Identifier.FileIndex();
public Utf8GamePath GamePath()
{
return ObjectType switch
{
ObjectType.Accessory => Utf8GamePath.FromString(GamePaths.Accessory.Imc.Path(PrimaryId), out var p) ? p : Utf8GamePath.Empty,
ObjectType.Equipment => Utf8GamePath.FromString(GamePaths.Equipment.Imc.Path(PrimaryId), out var p) ? p : Utf8GamePath.Empty,
ObjectType.DemiHuman => Utf8GamePath.FromString(GamePaths.DemiHuman.Imc.Path(PrimaryId, SecondaryId), out var p)
? p
: Utf8GamePath.Empty,
ObjectType.Monster => Utf8GamePath.FromString(GamePaths.Monster.Imc.Path(PrimaryId, SecondaryId), out var p)
? p
: Utf8GamePath.Empty,
ObjectType.Weapon => Utf8GamePath.FromString(GamePaths.Weapon.Imc.Path(PrimaryId, SecondaryId), out var p) ? p : Utf8GamePath.Empty,
_ => throw new NotImplementedException(),
};
}
=> Identifier.GamePath();
public bool Apply(ImcFile file)
=> file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
public bool Validate(bool withMaterial)
{
switch (ObjectType)
{
case ObjectType.Accessory:
case ObjectType.Equipment:
if (BodySlot is not BodySlot.Unknown)
return false;
if (!EquipSlot.IsEquipment() && !EquipSlot.IsAccessory())
return false;
if (SecondaryId != 0)
return false;
break;
case ObjectType.DemiHuman:
if (BodySlot is not BodySlot.Unknown)
return false;
if (!EquipSlot.IsEquipment() && !EquipSlot.IsAccessory())
return false;
break;
default:
if (!Enum.IsDefined(BodySlot))
return false;
if (EquipSlot is not EquipSlot.Unknown)
return false;
if (!Enum.IsDefined(ObjectType))
return false;
break;
}
if (!Identifier.Validate())
return false;
if (withMaterial && Entry.MaterialId == 0)
return false;

View file

@ -27,6 +27,7 @@ public unsafe class MetaFileManager
internal readonly ValidityChecker ValidityChecker;
internal readonly ObjectIdentification Identifier;
internal readonly FileCompactor Compactor;
internal readonly ImcChecker ImcChecker;
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
@ -40,6 +41,7 @@ public unsafe class MetaFileManager
ValidityChecker = validityChecker;
Identifier = identifier;
Compactor = compactor;
ImcChecker = new ImcChecker(this);
interop.InitializeFromAttributes(this);
}