mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-23 08:17:59 +01:00
Make IMC handling even better.
This commit is contained in:
parent
65627b5002
commit
4743acf767
15 changed files with 437 additions and 459 deletions
36
Penumbra/Meta/ImcChecker.cs
Normal file
36
Penumbra/Meta/ImcChecker.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue