mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-20 15:44:22 +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
|
|
@ -1 +1 @@
|
||||||
Subproject commit e8220a0a74e9480330e98ed7ca462353434b9649
|
Subproject commit ec35e66499eb388b4e7917e4fae4615218d33335
|
||||||
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,
|
EquipSlot EquipSlot,
|
||||||
BodySlot BodySlot) : IMetaIdentifier, IComparable<ImcIdentifier>
|
BodySlot BodySlot) : IMetaIdentifier, IComparable<ImcIdentifier>
|
||||||
{
|
{
|
||||||
|
public static readonly ImcIdentifier Default = new(EquipSlot.Body, 1, (Variant)1);
|
||||||
|
|
||||||
public ImcIdentifier(EquipSlot slot, PrimaryId primaryId, ushort variant)
|
public ImcIdentifier(EquipSlot slot, PrimaryId primaryId, ushort variant)
|
||||||
: this(primaryId, (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue),
|
: this(primaryId, (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue),
|
||||||
slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot,
|
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)
|
: 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)
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
{
|
{
|
||||||
var path = ObjectType switch
|
var path = ObjectType switch
|
||||||
|
|
@ -137,9 +142,12 @@ public readonly record struct ImcIdentifier(
|
||||||
return b != 0 ? b : Variant.Id.CompareTo(other.Variant.Id);
|
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 primaryId = new PrimaryId(jObj["PrimaryId"]?.ToObject<ushort>() ?? 0);
|
||||||
var variant = jObj["Variant"]?.ToObject<ushort>() ?? 0;
|
var variant = jObj["Variant"]?.ToObject<ushort>() ?? 0;
|
||||||
if (variant > byte.MaxValue)
|
if (variant > byte.MaxValue)
|
||||||
|
|
@ -178,12 +186,12 @@ public readonly record struct ImcIdentifier(
|
||||||
|
|
||||||
public JObject AddToJson(JObject jObj)
|
public JObject AddToJson(JObject jObj)
|
||||||
{
|
{
|
||||||
jObj["ObjectType"] = ObjectType.ToString();
|
jObj["ObjectType"] = ObjectType.ToString();
|
||||||
jObj["PrimaryId"] = PrimaryId.Id;
|
jObj["PrimaryId"] = PrimaryId.Id;
|
||||||
jObj["PrimaryId"] = SecondaryId.Id;
|
jObj["SecondaryId"] = SecondaryId.Id;
|
||||||
jObj["Variant"] = Variant.Id;
|
jObj["Variant"] = Variant.Id;
|
||||||
jObj["EquipSlot"] = EquipSlot.ToString();
|
jObj["EquipSlot"] = EquipSlot.ToString();
|
||||||
jObj["BodySlot"] = BodySlot.ToString();
|
jObj["BodySlot"] = BodySlot.ToString();
|
||||||
return jObj;
|
return jObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,171 +12,96 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||||
{
|
{
|
||||||
public ImcEntry Entry { get; private init; }
|
[JsonIgnore]
|
||||||
public PrimaryId PrimaryId { get; private init; }
|
public ImcIdentifier Identifier { get; private init; }
|
||||||
public PrimaryId SecondaryId { get; private init; }
|
|
||||||
public Variant Variant { 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))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ObjectType ObjectType { get; private init; }
|
public ObjectType ObjectType
|
||||||
|
=> Identifier.ObjectType;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot EquipSlot { get; private init; }
|
public EquipSlot EquipSlot
|
||||||
|
=> Identifier.EquipSlot;
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public BodySlot BodySlot { get; private init; }
|
public BodySlot BodySlot
|
||||||
|
=> Identifier.BodySlot;
|
||||||
|
|
||||||
public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry)
|
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;
|
Identifier = identifier;
|
||||||
PrimaryId = primaryId;
|
Entry = entry;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Variants were initially ushorts but got shortened to bytes.
|
// Variants were initially ushorts but got shortened to bytes.
|
||||||
// There are still some manipulations around that have values > 255 for variant,
|
// 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,
|
// 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.
|
// and clamp the variant to 255.
|
||||||
[JsonConstructor]
|
[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)
|
EquipSlot equipSlot, ImcEntry entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
ObjectType = objectType;
|
var v = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
|
||||||
PrimaryId = primaryId;
|
Identifier = objectType switch
|
||||||
Variant = (Variant)Math.Clamp(variant, (ushort)0, byte.MaxValue);
|
|
||||||
|
|
||||||
if (objectType is ObjectType.Accessory or ObjectType.Equipment)
|
|
||||||
{
|
{
|
||||||
BodySlot = variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown;
|
ObjectType.Accessory or ObjectType.Equipment => new ImcIdentifier(primaryId, v, objectType, 0, equipSlot,
|
||||||
SecondaryId = 0;
|
variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
|
||||||
EquipSlot = equipSlot;
|
ObjectType.DemiHuman => new ImcIdentifier(primaryId, v, objectType, secondaryId,
|
||||||
}
|
equipSlot == EquipSlot.Unknown ? EquipSlot.Head : equipSlot, variant > byte.MaxValue ? BodySlot.Body : BodySlot.Unknown),
|
||||||
else if (objectType is ObjectType.DemiHuman)
|
_ => new ImcIdentifier(primaryId, v, objectType, secondaryId, equipSlot == EquipSlot.Unknown ? EquipSlot.Head : equipSlot,
|
||||||
{
|
bodySlot),
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImcManipulation Copy(ImcEntry entry)
|
public ImcManipulation Copy(ImcEntry entry)
|
||||||
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant.Id, EquipSlot, entry);
|
=> new(Identifier, entry);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
=> Identifier.ToString();
|
||||||
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
|
|
||||||
: $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}";
|
|
||||||
|
|
||||||
public bool Equals(ImcManipulation other)
|
public bool Equals(ImcManipulation other)
|
||||||
=> PrimaryId == other.PrimaryId
|
=> Identifier == other.Identifier;
|
||||||
&& Variant == other.Variant
|
|
||||||
&& SecondaryId == other.SecondaryId
|
|
||||||
&& ObjectType == other.ObjectType
|
|
||||||
&& EquipSlot == other.EquipSlot
|
|
||||||
&& BodySlot == other.BodySlot;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
=> obj is ImcManipulation other && Equals(other);
|
=> obj is ImcManipulation other && Equals(other);
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> HashCode.Combine(PrimaryId, Variant, SecondaryId, (int)ObjectType, (int)EquipSlot, (int)BodySlot);
|
=> Identifier.GetHashCode();
|
||||||
|
|
||||||
public int CompareTo(ImcManipulation other)
|
public int CompareTo(ImcManipulation other)
|
||||||
{
|
=> Identifier.CompareTo(other.Identifier);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MetaIndex FileIndex()
|
public MetaIndex FileIndex()
|
||||||
=> (MetaIndex)(-1);
|
=> Identifier.FileIndex();
|
||||||
|
|
||||||
public Utf8GamePath GamePath()
|
public Utf8GamePath GamePath()
|
||||||
{
|
=> Identifier.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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Apply(ImcFile file)
|
public bool Apply(ImcFile file)
|
||||||
=> file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
|
=> file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry);
|
||||||
|
|
||||||
public bool Validate(bool withMaterial)
|
public bool Validate(bool withMaterial)
|
||||||
{
|
{
|
||||||
switch (ObjectType)
|
if (!Identifier.Validate())
|
||||||
{
|
return false;
|
||||||
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 (withMaterial && Entry.MaterialId == 0)
|
if (withMaterial && Entry.MaterialId == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public unsafe class MetaFileManager
|
||||||
internal readonly ValidityChecker ValidityChecker;
|
internal readonly ValidityChecker ValidityChecker;
|
||||||
internal readonly ObjectIdentification Identifier;
|
internal readonly ObjectIdentification Identifier;
|
||||||
internal readonly FileCompactor Compactor;
|
internal readonly FileCompactor Compactor;
|
||||||
|
internal readonly ImcChecker ImcChecker;
|
||||||
|
|
||||||
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
|
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
|
||||||
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
|
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
|
||||||
|
|
@ -40,6 +41,7 @@ public unsafe class MetaFileManager
|
||||||
ValidityChecker = validityChecker;
|
ValidityChecker = validityChecker;
|
||||||
Identifier = identifier;
|
Identifier = identifier;
|
||||||
Compactor = compactor;
|
Compactor = compactor;
|
||||||
|
ImcChecker = new ImcChecker(this);
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,21 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods.Settings;
|
using Penumbra.Mods.Settings;
|
||||||
using Penumbra.Mods.SubMods;
|
using Penumbra.Mods.SubMods;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.ModsTab;
|
|
||||||
using Penumbra.UI.ModsTab.Groups;
|
using Penumbra.UI.ModsTab.Groups;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods.Groups;
|
namespace Penumbra.Mods.Groups;
|
||||||
|
|
||||||
public class ImcModGroup(Mod mod) : IModGroup
|
public class ImcModGroup(Mod mod) : IModGroup
|
||||||
{
|
{
|
||||||
public const int DisabledIndex = 60;
|
|
||||||
|
|
||||||
public Mod Mod { get; } = mod;
|
public Mod Mod { get; } = mod;
|
||||||
public string Name { get; set; } = "Option";
|
public string Name { get; set; } = "Option";
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
@ -33,33 +29,35 @@ public class ImcModGroup(Mod mod) : IModGroup
|
||||||
public ModPriority Priority { get; set; } = ModPriority.Default;
|
public ModPriority Priority { get; set; } = ModPriority.Default;
|
||||||
public Setting DefaultSettings { get; set; } = Setting.Zero;
|
public Setting DefaultSettings { get; set; } = Setting.Zero;
|
||||||
|
|
||||||
public PrimaryId PrimaryId;
|
public ImcIdentifier Identifier;
|
||||||
public SecondaryId SecondaryId;
|
public ImcEntry DefaultEntry;
|
||||||
public ObjectType ObjectType;
|
|
||||||
public BodySlot BodySlot;
|
|
||||||
public EquipSlot EquipSlot;
|
|
||||||
public Variant Variant;
|
|
||||||
|
|
||||||
public ImcEntry DefaultEntry;
|
|
||||||
|
|
||||||
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
public FullPath? FindBestMatch(Utf8GamePath gamePath)
|
||||||
=> null;
|
=> null;
|
||||||
|
|
||||||
private bool _canBeDisabled = false;
|
private bool _canBeDisabled;
|
||||||
|
|
||||||
public bool CanBeDisabled
|
public bool CanBeDisabled
|
||||||
{
|
{
|
||||||
get => _canBeDisabled;
|
get => OptionData.Any(m => m.IsDisableSubMod);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_canBeDisabled = value;
|
_canBeDisabled = value;
|
||||||
if (!value)
|
if (!value)
|
||||||
|
{
|
||||||
|
OptionData.RemoveAll(m => m.IsDisableSubMod);
|
||||||
DefaultSettings = FixSetting(DefaultSettings);
|
DefaultSettings = FixSetting(DefaultSettings);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!OptionData.Any(m => m.IsDisableSubMod))
|
||||||
|
OptionData.Add(ImcSubMod.DisableSubMod(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DefaultDisabled
|
public bool DefaultDisabled
|
||||||
=> _canBeDisabled && DefaultSettings.HasFlag(DisabledIndex);
|
=> IsDisabled(DefaultSettings);
|
||||||
|
|
||||||
public IModOption? AddOption(string name, string description = "")
|
public IModOption? AddOption(string name, string description = "")
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +84,7 @@ public class ImcModGroup(Mod mod) : IModGroup
|
||||||
=> [];
|
=> [];
|
||||||
|
|
||||||
public bool IsOption
|
public bool IsOption
|
||||||
=> CanBeDisabled || OptionData.Count > 0;
|
=> OptionData.Count > 0;
|
||||||
|
|
||||||
public int GetIndex()
|
public int GetIndex()
|
||||||
=> ModGroup.GetIndex(this);
|
=> ModGroup.GetIndex(this);
|
||||||
|
|
@ -94,6 +92,128 @@ public class ImcModGroup(Mod mod) : IModGroup
|
||||||
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
|
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
|
||||||
=> new ImcModGroupEditDrawer(editDrawer, this);
|
=> new ImcModGroupEditDrawer(editDrawer, this);
|
||||||
|
|
||||||
|
public ImcManipulation GetManip(ushort mask)
|
||||||
|
=> new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, Identifier.Variant.Id,
|
||||||
|
Identifier.EquipSlot, DefaultEntry with { AttributeMask = mask });
|
||||||
|
|
||||||
|
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
||||||
|
{
|
||||||
|
if (IsDisabled(setting))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mask = GetCurrentMask(setting);
|
||||||
|
var imc = GetManip(mask);
|
||||||
|
manipulations.Add(imc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
||||||
|
=> Identifier.AddChangedItems(identifier, changedItems);
|
||||||
|
|
||||||
|
public Setting FixSetting(Setting setting)
|
||||||
|
=> new(setting.Value & ((1ul << OptionData.Count) - 1));
|
||||||
|
|
||||||
|
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
|
||||||
|
{
|
||||||
|
ModSaveGroup.WriteJsonBase(jWriter, this);
|
||||||
|
var jObj = Identifier.AddToJson(new JObject());
|
||||||
|
jWriter.WritePropertyName(nameof(Identifier));
|
||||||
|
jObj.WriteTo(jWriter);
|
||||||
|
jWriter.WritePropertyName(nameof(DefaultEntry));
|
||||||
|
serializer.Serialize(jWriter, DefaultEntry);
|
||||||
|
jWriter.WritePropertyName("Options");
|
||||||
|
jWriter.WriteStartArray();
|
||||||
|
foreach (var option in OptionData)
|
||||||
|
{
|
||||||
|
jWriter.WriteStartObject();
|
||||||
|
SubMod.WriteModOption(jWriter, option);
|
||||||
|
if (option.IsDisableSubMod)
|
||||||
|
{
|
||||||
|
jWriter.WritePropertyName(nameof(option.IsDisableSubMod));
|
||||||
|
jWriter.WriteValue(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jWriter.WritePropertyName(nameof(option.AttributeMask));
|
||||||
|
jWriter.WriteValue(option.AttributeMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
jWriter.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
jWriter.WriteEndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int Redirections, int Swaps, int Manips) GetCounts()
|
||||||
|
=> (0, 0, 1);
|
||||||
|
|
||||||
|
public static ImcModGroup? Load(Mod mod, JObject json)
|
||||||
|
{
|
||||||
|
var options = json["Options"];
|
||||||
|
var identifier = ImcIdentifier.FromJson(json[nameof(Identifier)] as JObject);
|
||||||
|
var ret = new ImcModGroup(mod)
|
||||||
|
{
|
||||||
|
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
||||||
|
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
||||||
|
Priority = json[nameof(Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default,
|
||||||
|
DefaultEntry = json[nameof(DefaultEntry)]?.ToObject<ImcEntry>() ?? new ImcEntry(),
|
||||||
|
};
|
||||||
|
if (ret.Name.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!identifier.HasValue || ret.DefaultEntry.MaterialId == 0)
|
||||||
|
{
|
||||||
|
Penumbra.Messager.NotificationMessage($"Could not add IMC group {ret.Name} because the associated IMC Entry is invalid.",
|
||||||
|
NotificationType.Warning);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rollingMask = ret.DefaultEntry.AttributeMask;
|
||||||
|
if (options != null)
|
||||||
|
foreach (var child in options.Children())
|
||||||
|
{
|
||||||
|
var subMod = new ImcSubMod(ret, child);
|
||||||
|
|
||||||
|
if (subMod.IsDisableSubMod)
|
||||||
|
ret._canBeDisabled = true;
|
||||||
|
|
||||||
|
if (subMod.IsDisableSubMod && ret.OptionData.FirstOrDefault(m => m.IsDisableSubMod) is { } disable)
|
||||||
|
{
|
||||||
|
Penumbra.Messager.NotificationMessage(
|
||||||
|
$"Could not add IMC option {subMod.Name} to {ret.Name} because it already contains {disable.Name} as disable option.",
|
||||||
|
NotificationType.Warning);
|
||||||
|
}
|
||||||
|
else if ((subMod.AttributeMask & rollingMask) != 0)
|
||||||
|
{
|
||||||
|
Penumbra.Messager.NotificationMessage(
|
||||||
|
$"Could not add IMC option {subMod.Name} to {ret.Name} because it contains attributes already in use.",
|
||||||
|
NotificationType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rollingMask |= subMod.AttributeMask;
|
||||||
|
ret.OptionData.Add(subMod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Identifier = identifier.Value;
|
||||||
|
ret.DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<Setting>() ?? Setting.Zero;
|
||||||
|
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDisabled(Setting setting)
|
||||||
|
{
|
||||||
|
if (!CanBeDisabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var idx = OptionData.IndexOf(m => m.IsDisableSubMod);
|
||||||
|
if (idx >= 0)
|
||||||
|
return setting.HasFlag(idx);
|
||||||
|
|
||||||
|
Penumbra.Log.Warning($"A IMC Group should be able to be disabled, but does not contain a disable option.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private ushort GetCurrentMask(Setting setting)
|
private ushort GetCurrentMask(Setting setting)
|
||||||
{
|
{
|
||||||
var mask = DefaultEntry.AttributeMask;
|
var mask = DefaultEntry.AttributeMask;
|
||||||
|
|
@ -108,101 +228,4 @@ public class ImcModGroup(Mod mod) : IModGroup
|
||||||
|
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ushort GetFullMask()
|
|
||||||
=> GetCurrentMask(Setting.AllBits(63));
|
|
||||||
|
|
||||||
public ImcManipulation GetManip(ushort mask)
|
|
||||||
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId.Id, Variant.Id, EquipSlot,
|
|
||||||
DefaultEntry with { AttributeMask = mask });
|
|
||||||
|
|
||||||
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
|
|
||||||
{
|
|
||||||
if (CanBeDisabled && setting.HasFlag(DisabledIndex))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var mask = GetCurrentMask(setting);
|
|
||||||
var imc = GetManip(mask);
|
|
||||||
manipulations.Add(imc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
|
|
||||||
=> identifier.MetaChangedItems(changedItems, GetManip(0));
|
|
||||||
|
|
||||||
public Setting FixSetting(Setting setting)
|
|
||||||
=> new(setting.Value & (((1ul << OptionData.Count) - 1) | (CanBeDisabled ? 1ul << DisabledIndex : 0)));
|
|
||||||
|
|
||||||
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
|
|
||||||
{
|
|
||||||
ModSaveGroup.WriteJsonBase(jWriter, this);
|
|
||||||
jWriter.WritePropertyName(nameof(ObjectType));
|
|
||||||
jWriter.WriteValue(ObjectType.ToString());
|
|
||||||
jWriter.WritePropertyName(nameof(BodySlot));
|
|
||||||
jWriter.WriteValue(BodySlot.ToString());
|
|
||||||
jWriter.WritePropertyName(nameof(EquipSlot));
|
|
||||||
jWriter.WriteValue(EquipSlot.ToString());
|
|
||||||
jWriter.WritePropertyName(nameof(PrimaryId));
|
|
||||||
jWriter.WriteValue(PrimaryId.Id);
|
|
||||||
jWriter.WritePropertyName(nameof(SecondaryId));
|
|
||||||
jWriter.WriteValue(SecondaryId.Id);
|
|
||||||
jWriter.WritePropertyName(nameof(Variant));
|
|
||||||
jWriter.WriteValue(Variant.Id);
|
|
||||||
jWriter.WritePropertyName(nameof(DefaultEntry));
|
|
||||||
serializer.Serialize(jWriter, DefaultEntry);
|
|
||||||
jWriter.WritePropertyName("Options");
|
|
||||||
jWriter.WriteStartArray();
|
|
||||||
foreach (var option in OptionData)
|
|
||||||
{
|
|
||||||
jWriter.WriteStartObject();
|
|
||||||
SubMod.WriteModOption(jWriter, option);
|
|
||||||
jWriter.WritePropertyName(nameof(option.AttributeMask));
|
|
||||||
jWriter.WriteValue(option.AttributeMask);
|
|
||||||
jWriter.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
jWriter.WriteEndArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public (int Redirections, int Swaps, int Manips) GetCounts()
|
|
||||||
=> (0, 0, 1);
|
|
||||||
|
|
||||||
public static ImcModGroup? Load(Mod mod, JObject json)
|
|
||||||
{
|
|
||||||
var options = json["Options"];
|
|
||||||
var ret = new ImcModGroup(mod)
|
|
||||||
{
|
|
||||||
Name = json[nameof(Name)]?.ToObject<string>() ?? string.Empty,
|
|
||||||
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
|
|
||||||
Priority = json[nameof(Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default,
|
|
||||||
DefaultSettings = json[nameof(DefaultSettings)]?.ToObject<Setting>() ?? Setting.Zero,
|
|
||||||
ObjectType = json[nameof(ObjectType)]?.ToObject<ObjectType>() ?? ObjectType.Unknown,
|
|
||||||
BodySlot = json[nameof(BodySlot)]?.ToObject<BodySlot>() ?? BodySlot.Unknown,
|
|
||||||
EquipSlot = json[nameof(EquipSlot)]?.ToObject<EquipSlot>() ?? EquipSlot.Unknown,
|
|
||||||
PrimaryId = new PrimaryId(json[nameof(PrimaryId)]?.ToObject<ushort>() ?? 0),
|
|
||||||
SecondaryId = new SecondaryId(json[nameof(SecondaryId)]?.ToObject<ushort>() ?? 0),
|
|
||||||
Variant = new Variant(json[nameof(Variant)]?.ToObject<byte>() ?? 0),
|
|
||||||
CanBeDisabled = json[nameof(CanBeDisabled)]?.ToObject<bool>() ?? false,
|
|
||||||
DefaultEntry = json[nameof(DefaultEntry)]?.ToObject<ImcEntry>() ?? new ImcEntry(),
|
|
||||||
};
|
|
||||||
if (ret.Name.Length == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (options != null)
|
|
||||||
foreach (var child in options.Children())
|
|
||||||
{
|
|
||||||
var subMod = new ImcSubMod(ret, child);
|
|
||||||
ret.OptionData.Add(subMod);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!new ImcManipulation(ret.ObjectType, ret.BodySlot, ret.PrimaryId, ret.SecondaryId.Id, ret.Variant.Id, ret.EquipSlot,
|
|
||||||
ret.DefaultEntry).Validate(true))
|
|
||||||
{
|
|
||||||
Penumbra.Messager.NotificationMessage($"Could not add IMC group because the associated IMC Entry is invalid.",
|
|
||||||
NotificationType.Warning);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using Penumbra.Mods.Groups;
|
||||||
using Penumbra.Mods.Settings;
|
using Penumbra.Mods.Settings;
|
||||||
using Penumbra.Mods.SubMods;
|
using Penumbra.Mods.SubMods;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using static FFXIVClientStructs.FFXIV.Client.UI.Misc.ConfigModule;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods.Manager.OptionEditor;
|
namespace Penumbra.Mods.Manager.OptionEditor;
|
||||||
|
|
||||||
|
|
@ -15,13 +14,13 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
|
||||||
: ModOptionEditor<ImcModGroup, ImcSubMod>(communicator, saveService, config), IService
|
: ModOptionEditor<ImcModGroup, ImcSubMod>(communicator, saveService, config), IService
|
||||||
{
|
{
|
||||||
/// <summary> Add a new, empty imc group with the given manipulation data. </summary>
|
/// <summary> Add a new, empty imc group with the given manipulation data. </summary>
|
||||||
public ImcModGroup? AddModGroup(Mod mod, string newName, ImcManipulation manip, SaveType saveType = SaveType.ImmediateSync)
|
public ImcModGroup? AddModGroup(Mod mod, string newName, ImcIdentifier identifier, ImcEntry defaultEntry, SaveType saveType = SaveType.ImmediateSync)
|
||||||
{
|
{
|
||||||
if (!ModGroupEditor.VerifyFileName(mod, null, newName, true))
|
if (!ModGroupEditor.VerifyFileName(mod, null, newName, true))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1;
|
var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1;
|
||||||
var group = CreateGroup(mod, newName, manip, maxPriority);
|
var group = CreateGroup(mod, newName, identifier, defaultEntry, maxPriority);
|
||||||
mod.Groups.Add(group);
|
mod.Groups.Add(group);
|
||||||
SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport));
|
SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport));
|
||||||
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, group, null, null, -1);
|
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, group, null, null, -1);
|
||||||
|
|
@ -97,19 +96,14 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private static ImcModGroup CreateGroup(Mod mod, string newName, ImcManipulation manip, ModPriority priority,
|
private static ImcModGroup CreateGroup(Mod mod, string newName, ImcIdentifier identifier, ImcEntry defaultEntry, ModPriority priority,
|
||||||
SaveType saveType = SaveType.ImmediateSync)
|
SaveType saveType = SaveType.ImmediateSync)
|
||||||
=> new(mod)
|
=> new(mod)
|
||||||
{
|
{
|
||||||
Name = newName,
|
Name = newName,
|
||||||
Priority = priority,
|
Priority = priority,
|
||||||
ObjectType = manip.ObjectType,
|
Identifier = identifier,
|
||||||
EquipSlot = manip.EquipSlot,
|
DefaultEntry = defaultEntry,
|
||||||
BodySlot = manip.BodySlot,
|
|
||||||
PrimaryId = manip.PrimaryId,
|
|
||||||
SecondaryId = manip.SecondaryId.Id,
|
|
||||||
Variant = manip.Variant,
|
|
||||||
DefaultEntry = manip.Entry,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override ImcSubMod? CloneOption(ImcModGroup group, IModOption option)
|
protected override ImcSubMod? CloneOption(ImcModGroup group, IModOption option)
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ public class ModGroupEditor(
|
||||||
{
|
{
|
||||||
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
|
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
|
||||||
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
|
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
|
||||||
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, saveType),
|
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,23 @@ public class ImcSubMod(ImcModGroup group) : IModOption
|
||||||
: this(group)
|
: this(group)
|
||||||
{
|
{
|
||||||
SubMod.LoadOptionData(json, this);
|
SubMod.LoadOptionData(json, this);
|
||||||
AttributeMask = (ushort)((json[nameof(AttributeMask)]?.ToObject<ushort>() ?? 0) & ImcEntry.AttributesMask);
|
AttributeMask = (ushort)((json[nameof(AttributeMask)]?.ToObject<ushort>() ?? 0) & ImcEntry.AttributesMask);
|
||||||
|
IsDisableSubMod = json[nameof(IsDisableSubMod)]?.ToObject<bool>() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ImcSubMod DisableSubMod(ImcModGroup group)
|
||||||
|
=> new(group)
|
||||||
|
{
|
||||||
|
Name = "Disable",
|
||||||
|
AttributeMask = 0,
|
||||||
|
IsDisableSubMod = true,
|
||||||
|
};
|
||||||
|
|
||||||
public Mod Mod
|
public Mod Mod
|
||||||
=> Group.Mod;
|
=> Group.Mod;
|
||||||
|
|
||||||
public ushort AttributeMask;
|
public ushort AttributeMask;
|
||||||
|
public bool IsDisableSubMod { get; private init; }
|
||||||
|
|
||||||
Mod IModOption.Mod
|
Mod IModOption.Mod
|
||||||
=> Mod;
|
=> Mod;
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,8 @@ public static class StaticServiceManager
|
||||||
.AddSingleton<CreateFileWHook>()
|
.AddSingleton<CreateFileWHook>()
|
||||||
.AddSingleton<ResidentResourceManager>()
|
.AddSingleton<ResidentResourceManager>()
|
||||||
.AddSingleton<FontReloader>()
|
.AddSingleton<FontReloader>()
|
||||||
.AddSingleton<RedrawService>();
|
.AddSingleton<RedrawService>()
|
||||||
|
.AddSingleton(p => p.GetRequiredService<MetaFileManager>().ImcChecker);
|
||||||
|
|
||||||
private static ServiceManager AddConfiguration(this ServiceManager services)
|
private static ServiceManager AddConfiguration(this ServiceManager services)
|
||||||
=> services.AddSingleton<Configuration>()
|
=> services.AddSingleton<Configuration>()
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ public partial class ModEditWindow
|
||||||
private const string ModelSetIdTooltip =
|
private const string ModelSetIdTooltip =
|
||||||
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
||||||
|
|
||||||
private const string PrimaryIdTooltip =
|
|
||||||
"Primary ID - You can usually find this as the 'x####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
|
||||||
|
|
||||||
private const string ModelSetIdTooltipShort = "Model Set ID";
|
private const string ModelSetIdTooltipShort = "Model Set ID";
|
||||||
private const string EquipSlotTooltip = "Equip Slot";
|
private const string EquipSlotTooltip = "Equip Slot";
|
||||||
private const string ModelRaceTooltip = "Model Race";
|
private const string ModelRaceTooltip = "Model Race";
|
||||||
|
|
@ -316,7 +313,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
private static class ImcRow
|
private static class ImcRow
|
||||||
{
|
{
|
||||||
private static ImcManipulation _new = new(EquipSlot.Head, 1, 1, new ImcEntry());
|
private static ImcIdentifier _newIdentifier = ImcIdentifier.Default;
|
||||||
|
|
||||||
private static float IdWidth
|
private static float IdWidth
|
||||||
=> 80 * UiHelpers.Scale;
|
=> 80 * UiHelpers.Scale;
|
||||||
|
|
@ -324,75 +321,60 @@ public partial class ModEditWindow
|
||||||
private static float SmallIdWidth
|
private static float SmallIdWidth
|
||||||
=> 45 * UiHelpers.Scale;
|
=> 45 * UiHelpers.Scale;
|
||||||
|
|
||||||
/// <summary> Convert throwing to null-return if the file does not exist. </summary>
|
|
||||||
private static ImcEntry? GetDefault(MetaFileManager metaFileManager, ImcManipulation imc)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return ImcFile.GetDefault(metaFileManager, imc.GamePath(), imc.EquipSlot, imc.Variant, out _);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize,
|
CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize,
|
||||||
editor.MetaEditor.Imc.Select(m => (MetaManipulation)m));
|
editor.MetaEditor.Imc.Select(m => (MetaManipulation)m));
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var defaultEntry = GetDefault(metaFileManager, _new);
|
var (defaultEntry, fileExists, _) = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true);
|
||||||
var canAdd = defaultEntry != null && editor.MetaEditor.CanAdd(_new);
|
var manip = (MetaManipulation)new ImcManipulation(_newIdentifier, defaultEntry);
|
||||||
var tt = canAdd ? "Stage this edit." : defaultEntry == null ? "This IMC file does not exist." : "This entry is already edited.";
|
var canAdd = fileExists && editor.MetaEditor.CanAdd(manip);
|
||||||
defaultEntry ??= new ImcEntry();
|
var tt = canAdd ? "Stage this edit." : !fileExists ? "This IMC file does not exist." : "This entry is already edited.";
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true))
|
||||||
editor.MetaEditor.Add(_new.Copy(defaultEntry.Value));
|
editor.MetaEditor.Add(manip);
|
||||||
|
|
||||||
// Identifier
|
// Identifier
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var change = ImcManipulationDrawer.DrawObjectType(ref _new);
|
var change = ImcManipulationDrawer.DrawObjectType(ref _newIdentifier);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
change |= ImcManipulationDrawer.DrawPrimaryId(ref _new);
|
change |= ImcManipulationDrawer.DrawPrimaryId(ref _newIdentifier);
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
// Equipment and accessories are slightly different imcs than other types.
|
// Equipment and accessories are slightly different imcs than other types.
|
||||||
if (_new.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
if (_newIdentifier.ObjectType is ObjectType.Equipment or ObjectType.Accessory)
|
||||||
change |= ImcManipulationDrawer.DrawSlot(ref _new);
|
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier);
|
||||||
else
|
else
|
||||||
change |= ImcManipulationDrawer.DrawSecondaryId(ref _new);
|
change |= ImcManipulationDrawer.DrawSecondaryId(ref _newIdentifier);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
change |= ImcManipulationDrawer.DrawVariant(ref _new);
|
change |= ImcManipulationDrawer.DrawVariant(ref _newIdentifier);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (_new.ObjectType is ObjectType.DemiHuman)
|
if (_newIdentifier.ObjectType is ObjectType.DemiHuman)
|
||||||
change |= ImcManipulationDrawer.DrawSlot(ref _new, 70);
|
change |= ImcManipulationDrawer.DrawSlot(ref _newIdentifier, 70);
|
||||||
else
|
else
|
||||||
ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0));
|
ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0));
|
||||||
|
|
||||||
if (change)
|
if (change)
|
||||||
_new = _new.Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry());
|
defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(_newIdentifier, true).Entry;
|
||||||
// Values
|
// Values
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
|
|
||||||
var entry = defaultEntry.Value;
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImcManipulationDrawer.DrawMaterialId(entry, ref entry, false);
|
ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref defaultEntry, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImcManipulationDrawer.DrawMaterialAnimationId(entry, ref entry, false);
|
ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref defaultEntry, false);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImcManipulationDrawer.DrawDecalId(entry, ref entry, false);
|
ImcManipulationDrawer.DrawDecalId(defaultEntry, ref defaultEntry, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImcManipulationDrawer.DrawVfxId(entry, ref entry, false);
|
ImcManipulationDrawer.DrawVfxId(defaultEntry, ref defaultEntry, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImcManipulationDrawer.DrawSoundId(entry, ref entry, false);
|
ImcManipulationDrawer.DrawSoundId(defaultEntry, ref defaultEntry, false);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImcManipulationDrawer.DrawAttributes(entry, ref entry);
|
ImcManipulationDrawer.DrawAttributes(defaultEntry, ref defaultEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize)
|
public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize)
|
||||||
|
|
@ -439,10 +421,9 @@ public partial class ModEditWindow
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||||
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y));
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var defaultEntry = GetDefault(metaFileManager, meta) ?? new ImcEntry();
|
var defaultEntry = metaFileManager.ImcChecker.GetDefaultEntry(meta.Identifier, true).Entry;
|
||||||
var newEntry = meta.Entry;
|
var newEntry = meta.Entry;
|
||||||
|
var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true);
|
||||||
var changes = ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref newEntry, true);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true);
|
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref newEntry, true);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Meta.Files;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
|
@ -17,20 +16,20 @@ namespace Penumbra.UI.ModsTab.Groups;
|
||||||
public class AddGroupDrawer : IUiService
|
public class AddGroupDrawer : IUiService
|
||||||
{
|
{
|
||||||
private string _groupName = string.Empty;
|
private string _groupName = string.Empty;
|
||||||
private bool _groupNameValid = false;
|
private bool _groupNameValid;
|
||||||
|
|
||||||
private ImcManipulation _imcManip = new(EquipSlot.Head, 1, 1, new ImcEntry());
|
private ImcIdentifier _imcIdentifier = ImcIdentifier.Default;
|
||||||
private ImcEntry _defaultEntry;
|
private ImcEntry _defaultEntry;
|
||||||
private bool _imcFileExists;
|
private bool _imcFileExists;
|
||||||
private bool _entryExists;
|
private bool _entryExists;
|
||||||
private bool _entryInvalid;
|
private bool _entryInvalid;
|
||||||
private readonly MetaFileManager _metaManager;
|
private readonly ImcChecker _imcChecker;
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
|
|
||||||
public AddGroupDrawer(MetaFileManager metaManager, ModManager modManager)
|
public AddGroupDrawer(ModManager modManager, ImcChecker imcChecker)
|
||||||
{
|
{
|
||||||
_metaManager = metaManager;
|
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
|
_imcChecker = imcChecker;
|
||||||
UpdateEntry();
|
UpdateEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +60,7 @@ public class AddGroupDrawer : IUiService
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _groupName);
|
_modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _groupName);
|
||||||
_groupName = string.Empty;
|
_groupName = string.Empty;
|
||||||
_groupNameValid = false;
|
_groupNameValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,35 +73,35 @@ public class AddGroupDrawer : IUiService
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_modManager.OptionEditor.AddModGroup(mod, GroupType.Multi, _groupName);
|
_modManager.OptionEditor.AddModGroup(mod, GroupType.Multi, _groupName);
|
||||||
_groupName = string.Empty;
|
_groupName = string.Empty;
|
||||||
_groupNameValid = false;
|
_groupNameValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawImcInput(float width)
|
private void DrawImcInput(float width)
|
||||||
{
|
{
|
||||||
var change = ImcManipulationDrawer.DrawObjectType(ref _imcManip, width);
|
var change = ImcManipulationDrawer.DrawObjectType(ref _imcIdentifier, width);
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
change |= ImcManipulationDrawer.DrawPrimaryId(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawPrimaryId(ref _imcIdentifier, width);
|
||||||
if (_imcManip.ObjectType is ObjectType.Weapon or ObjectType.Monster)
|
if (_imcIdentifier.ObjectType is ObjectType.Weapon or ObjectType.Monster)
|
||||||
{
|
{
|
||||||
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width);
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
change |= ImcManipulationDrawer.DrawVariant(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width);
|
||||||
}
|
}
|
||||||
else if (_imcManip.ObjectType is ObjectType.DemiHuman)
|
else if (_imcIdentifier.ObjectType is ObjectType.DemiHuman)
|
||||||
{
|
{
|
||||||
var quarterWidth = (width - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2;
|
var quarterWidth = (width - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2;
|
||||||
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawSecondaryId(ref _imcIdentifier, width);
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
change |= ImcManipulationDrawer.DrawSlot(ref _imcManip, quarterWidth);
|
change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, quarterWidth);
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
change |= ImcManipulationDrawer.DrawVariant(ref _imcManip, quarterWidth);
|
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, quarterWidth);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
change |= ImcManipulationDrawer.DrawSlot(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawSlot(ref _imcIdentifier, width);
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
change |= ImcManipulationDrawer.DrawVariant(ref _imcManip, width);
|
change |= ImcManipulationDrawer.DrawVariant(ref _imcIdentifier, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change)
|
if (change)
|
||||||
|
|
@ -125,8 +124,8 @@ public class AddGroupDrawer : IUiService
|
||||||
: "Add a new multi selection option group to this mod."u8,
|
: "Add a new multi selection option group to this mod."u8,
|
||||||
width, !_groupNameValid || _entryInvalid))
|
width, !_groupNameValid || _entryInvalid))
|
||||||
{
|
{
|
||||||
_modManager.OptionEditor.ImcEditor.AddModGroup(mod, _groupName, _imcManip);
|
_modManager.OptionEditor.ImcEditor.AddModGroup(mod, _groupName, _imcIdentifier, _defaultEntry);
|
||||||
_groupName = string.Empty;
|
_groupName = string.Empty;
|
||||||
_groupNameValid = false;
|
_groupNameValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,20 +141,7 @@ public class AddGroupDrawer : IUiService
|
||||||
|
|
||||||
private void UpdateEntry()
|
private void UpdateEntry()
|
||||||
{
|
{
|
||||||
try
|
(_defaultEntry, _imcFileExists, _entryExists) = _imcChecker.GetDefaultEntry(_imcIdentifier, false);
|
||||||
{
|
_entryInvalid = !_imcIdentifier.Validate() || _defaultEntry.MaterialId == 0 || !_entryExists;
|
||||||
_defaultEntry = ImcFile.GetDefault(_metaManager, _imcManip.GamePath(), _imcManip.EquipSlot, _imcManip.Variant,
|
|
||||||
out _entryExists);
|
|
||||||
_imcFileExists = true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
_defaultEntry = new ImcEntry();
|
|
||||||
_imcFileExists = false;
|
|
||||||
_entryExists = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_imcManip = _imcManip.Copy(_entryExists ? _defaultEntry : new ImcEntry());
|
|
||||||
_entryInvalid = !_imcManip.Validate(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Text;
|
using OtterGui.Text;
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods.Groups;
|
using Penumbra.Mods.Groups;
|
||||||
using Penumbra.Mods.Manager.OptionEditor;
|
using Penumbra.Mods.Manager.OptionEditor;
|
||||||
|
|
@ -16,59 +14,75 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
|
||||||
{
|
{
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
|
var identifier = group.Identifier;
|
||||||
|
var defaultEntry = editor.ImcChecker.GetDefaultEntry(identifier, true).Entry;
|
||||||
|
var entry = group.DefaultEntry;
|
||||||
|
var changes = false;
|
||||||
|
|
||||||
|
ImUtf8.TextFramed(identifier.ToString(), 0, editor.AvailableWidth, borderColor: ImGui.GetColorU32(ImGuiCol.Border));
|
||||||
|
|
||||||
using (ImUtf8.Group())
|
using (ImUtf8.Group())
|
||||||
{
|
{
|
||||||
ImUtf8.Text("Object Type"u8);
|
|
||||||
if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman)
|
|
||||||
ImUtf8.Text("Slot"u8);
|
|
||||||
ImUtf8.Text("Primary ID");
|
|
||||||
if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory)
|
|
||||||
ImUtf8.Text("Secondary ID");
|
|
||||||
ImUtf8.Text("Variant"u8);
|
|
||||||
|
|
||||||
ImUtf8.TextFrameAligned("Material ID"u8);
|
ImUtf8.TextFrameAligned("Material ID"u8);
|
||||||
ImUtf8.TextFrameAligned("Material Animation ID"u8);
|
|
||||||
ImUtf8.TextFrameAligned("Decal ID"u8);
|
|
||||||
ImUtf8.TextFrameAligned("VFX ID"u8);
|
ImUtf8.TextFrameAligned("VFX ID"u8);
|
||||||
|
ImUtf8.TextFrameAligned("Decal ID"u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
changes |= ImcManipulationDrawer.DrawMaterialId(defaultEntry, ref entry, true);
|
||||||
|
changes |= ImcManipulationDrawer.DrawVfxId(defaultEntry, ref entry, true);
|
||||||
|
changes |= ImcManipulationDrawer.DrawDecalId(defaultEntry, ref entry, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(0, editor.PriorityWidth);
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
ImUtf8.TextFrameAligned("Material Animation ID"u8);
|
||||||
ImUtf8.TextFrameAligned("Sound ID"u8);
|
ImUtf8.TextFrameAligned("Sound ID"u8);
|
||||||
ImUtf8.TextFrameAligned("Can Be Disabled"u8);
|
ImUtf8.TextFrameAligned("Can Be Disabled"u8);
|
||||||
ImUtf8.TextFrameAligned("Default Attributes"u8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
changes |= ImcManipulationDrawer.DrawMaterialAnimationId(defaultEntry, ref entry, true);
|
||||||
|
changes |= ImcManipulationDrawer.DrawSoundId(defaultEntry, ref entry, true);
|
||||||
|
var canBeDisabled = group.CanBeDisabled;
|
||||||
|
if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled))
|
||||||
|
editor.ModManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes)
|
||||||
|
editor.ModManager.OptionEditor.ImcEditor.ChangeDefaultEntry(group, entry);
|
||||||
|
|
||||||
|
ImGui.Dummy(Vector2.Zero);
|
||||||
|
DrawOptions();
|
||||||
var attributeCache = new ImcAttributeCache(group);
|
var attributeCache = new ImcAttributeCache(group);
|
||||||
|
DrawNewOption(attributeCache);
|
||||||
|
ImGui.Dummy(Vector2.Zero);
|
||||||
|
|
||||||
|
|
||||||
using (ImUtf8.Group())
|
using (ImUtf8.Group())
|
||||||
{
|
{
|
||||||
ImUtf8.Text(group.ObjectType.ToName());
|
ImUtf8.TextFrameAligned("Default Attributes"u8);
|
||||||
if (group.ObjectType is ObjectType.Equipment or ObjectType.Accessory or ObjectType.DemiHuman)
|
foreach (var option in group.OptionData.Where(o => !o.IsDisableSubMod))
|
||||||
ImUtf8.Text(group.EquipSlot.ToName());
|
ImUtf8.TextFrameAligned(option.Name);
|
||||||
ImUtf8.Text($"{group.PrimaryId.Id}");
|
|
||||||
if (group.ObjectType is not ObjectType.Equipment and not ObjectType.Accessory)
|
|
||||||
ImUtf8.Text($"{group.SecondaryId.Id}");
|
|
||||||
ImUtf8.Text($"{group.Variant.Id}");
|
|
||||||
|
|
||||||
ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialId}");
|
|
||||||
ImUtf8.TextFrameAligned($"{group.DefaultEntry.MaterialAnimationId}");
|
|
||||||
ImUtf8.TextFrameAligned($"{group.DefaultEntry.DecalId}");
|
|
||||||
ImUtf8.TextFrameAligned($"{group.DefaultEntry.VfxId}");
|
|
||||||
ImUtf8.TextFrameAligned($"{group.DefaultEntry.SoundId}");
|
|
||||||
|
|
||||||
var canBeDisabled = group.CanBeDisabled;
|
|
||||||
if (ImUtf8.Checkbox("##disabled"u8, ref canBeDisabled))
|
|
||||||
editor.ModManager.OptionEditor.ImcEditor.ChangeCanBeDisabled(group, canBeDisabled, SaveType.Queue);
|
|
||||||
|
|
||||||
var defaultDisabled = group.DefaultDisabled;
|
|
||||||
ImUtf8.SameLineInner();
|
|
||||||
if (ImUtf8.Checkbox("##defaultDisabled"u8, ref defaultDisabled))
|
|
||||||
editor.ModManager.OptionEditor.ChangeModGroupDefaultOption(group,
|
|
||||||
group.DefaultSettings.SetBit(ImcModGroup.DisabledIndex, defaultDisabled));
|
|
||||||
|
|
||||||
DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, group.DefaultEntry.AttributeMask, group);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImUtf8.SameLineInner();
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, group.DefaultEntry.AttributeMask, group);
|
||||||
|
foreach (var option in group.OptionData.Where(o => !o.IsDisableSubMod))
|
||||||
|
DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, option.AttributeMask, option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawOptions()
|
||||||
|
{
|
||||||
foreach (var (option, optionIdx) in group.OptionData.WithIndex())
|
foreach (var (option, optionIdx) in group.OptionData.WithIndex())
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(optionIdx);
|
using var id = ImRaii.PushId(optionIdx);
|
||||||
|
|
@ -83,56 +97,51 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
editor.DrawOptionDescription(option);
|
editor.DrawOptionDescription(option);
|
||||||
|
|
||||||
ImUtf8.SameLineInner();
|
if (!option.IsDisableSubMod)
|
||||||
editor.DrawOptionDelete(option);
|
|
||||||
|
|
||||||
ImUtf8.SameLineInner();
|
|
||||||
ImGui.Dummy(new Vector2(editor.PriorityWidth, 0));
|
|
||||||
|
|
||||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + editor.OptionIdxSelectable.X + ImUtf8.ItemInnerSpacing.X * 2 + ImUtf8.FrameHeight);
|
|
||||||
DrawAttributes(editor.ModManager.OptionEditor.ImcEditor, attributeCache, option.AttributeMask, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawNewOption(attributeCache);
|
|
||||||
return;
|
|
||||||
|
|
||||||
static void DrawAttributes(ImcModGroupEditor editor, in ImcAttributeCache cache, ushort mask, object data)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < ImcEntry.NumAttributes; ++i)
|
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(i);
|
ImUtf8.SameLineInner();
|
||||||
var value = (mask & 1 << i) != 0;
|
editor.DrawOptionDelete(option);
|
||||||
using (ImRaii.Disabled(!cache.CanChange(i)))
|
|
||||||
{
|
|
||||||
if (ImUtf8.Checkbox(TerminatedByteString.Empty, ref value))
|
|
||||||
{
|
|
||||||
if (data is ImcModGroup g)
|
|
||||||
editor.ChangeDefaultAttribute(g, cache, i, value);
|
|
||||||
else
|
|
||||||
editor.ChangeOptionAttribute((ImcSubMod)data, cache, i, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImUtf8.HoverTooltip($"{(char)('A' + i)}");
|
|
||||||
if (i != 9)
|
|
||||||
ImUtf8.SameLineInner();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawNewOption(in ImcAttributeCache cache)
|
private void DrawNewOption(in ImcAttributeCache cache)
|
||||||
{
|
{
|
||||||
if (cache.LowestUnsetMask == 0)
|
var dis = cache.LowestUnsetMask == 0;
|
||||||
return;
|
var name = editor.DrawNewOptionBase(group, group.Options.Count);
|
||||||
|
|
||||||
var name = editor.DrawNewOptionBase(group, group.Options.Count);
|
|
||||||
var validName = name.Length > 0;
|
var validName = name.Length > 0;
|
||||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
var tt = dis
|
||||||
|
? "No Free Attribute Slots for New Options..."u8
|
||||||
|
: validName
|
||||||
? "Add a new option to this group."u8
|
? "Add a new option to this group."u8
|
||||||
: "Please enter a name for the new option."u8, !validName))
|
: "Please enter a name for the new option."u8;
|
||||||
|
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, !validName || dis))
|
||||||
{
|
{
|
||||||
editor.ModManager.OptionEditor.ImcEditor.AddOption(group, cache, name);
|
editor.ModManager.OptionEditor.ImcEditor.AddOption(group, cache, name);
|
||||||
editor.NewOptionName = null;
|
editor.NewOptionName = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawAttributes(ImcModGroupEditor editor, in ImcAttributeCache cache, ushort mask, object data)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < ImcEntry.NumAttributes; ++i)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(i);
|
||||||
|
var value = (mask & (1 << i)) != 0;
|
||||||
|
using (ImRaii.Disabled(!cache.CanChange(i)))
|
||||||
|
{
|
||||||
|
if (ImUtf8.Checkbox(TerminatedByteString.Empty, ref value))
|
||||||
|
{
|
||||||
|
if (data is ImcModGroup g)
|
||||||
|
editor.ChangeDefaultAttribute(g, cache, i, value);
|
||||||
|
else
|
||||||
|
editor.ChangeOptionAttribute((ImcSubMod)data, cache, i, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImUtf8.HoverTooltip("ABCDEFGHIJ"u8.Slice(i, 1));
|
||||||
|
if (i != 9)
|
||||||
|
ImUtf8.SameLineInner();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using OtterGui.Raii;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using OtterGui.Text;
|
using OtterGui.Text;
|
||||||
using OtterGui.Text.EndObjects;
|
using OtterGui.Text.EndObjects;
|
||||||
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Groups;
|
using Penumbra.Mods.Groups;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
|
@ -22,11 +23,16 @@ public sealed class ModGroupEditDrawer(
|
||||||
ModManager modManager,
|
ModManager modManager,
|
||||||
Configuration config,
|
Configuration config,
|
||||||
FilenameService filenames,
|
FilenameService filenames,
|
||||||
DescriptionEditPopup descriptionPopup) : IUiService
|
DescriptionEditPopup descriptionPopup,
|
||||||
|
ImcChecker imcChecker) : IUiService
|
||||||
{
|
{
|
||||||
private static ReadOnlySpan<byte> DragDropLabel
|
private static ReadOnlySpan<byte> AcrossGroupsLabel
|
||||||
=> "##DragOption"u8;
|
=> "##DragOptionAcross"u8;
|
||||||
|
|
||||||
|
private static ReadOnlySpan<byte> InsideGroupLabel
|
||||||
|
=> "##DragOptionInside"u8;
|
||||||
|
|
||||||
|
internal readonly ImcChecker ImcChecker = imcChecker;
|
||||||
internal readonly ModManager ModManager = modManager;
|
internal readonly ModManager ModManager = modManager;
|
||||||
internal readonly Queue<Action> ActionQueue = new();
|
internal readonly Queue<Action> ActionQueue = new();
|
||||||
|
|
||||||
|
|
@ -50,6 +56,7 @@ public sealed class ModGroupEditDrawer(
|
||||||
|
|
||||||
private IModGroup? _dragDropGroup;
|
private IModGroup? _dragDropGroup;
|
||||||
private IModOption? _dragDropOption;
|
private IModOption? _dragDropOption;
|
||||||
|
private bool _draggingAcross;
|
||||||
|
|
||||||
public void Draw(Mod mod)
|
public void Draw(Mod mod)
|
||||||
{
|
{
|
||||||
|
|
@ -292,32 +299,30 @@ public sealed class ModGroupEditDrawer(
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Source(IModOption option)
|
private void Source(IModOption option)
|
||||||
{
|
{
|
||||||
if (option.Group is not ITexToolsGroup)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using var source = ImUtf8.DragDropSource();
|
using var source = ImUtf8.DragDropSource();
|
||||||
if (!source)
|
if (!source)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!DragDropSource.SetPayload(DragDropLabel))
|
var across = option.Group is ITexToolsGroup;
|
||||||
|
|
||||||
|
if (!DragDropSource.SetPayload(across ? AcrossGroupsLabel : InsideGroupLabel))
|
||||||
{
|
{
|
||||||
_dragDropGroup = option.Group;
|
_dragDropGroup = option.Group;
|
||||||
_dragDropOption = option;
|
_dragDropOption = option;
|
||||||
|
_draggingAcross = across;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TextUnformatted($"Dragging option {option.Name} from group {option.Group.Name}...");
|
ImUtf8.Text($"Dragging option {option.Name} from group {option.Group.Name}...");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Target(IModGroup group, int optionIdx)
|
private void Target(IModGroup group, int optionIdx)
|
||||||
{
|
{
|
||||||
if (group is not ITexToolsGroup)
|
if (_dragDropGroup != group
|
||||||
return;
|
&& (!_draggingAcross || (_dragDropGroup != null && group is MultiModGroup { Options.Count: >= IModGroup.MaxMultiOptions })))
|
||||||
|
|
||||||
if (_dragDropGroup != group && _dragDropGroup != null && group is MultiModGroup { Options.Count: >= IModGroup.MaxMultiOptions })
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using var target = ImRaii.DragDropTarget();
|
using var target = ImRaii.DragDropTarget();
|
||||||
if (!target.Success || !DragDropTarget.CheckPayload(DragDropLabel))
|
if (!target.Success || !DragDropTarget.CheckPayload(_draggingAcross ? AcrossGroupsLabel : InsideGroupLabel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_dragDropGroup != null && _dragDropOption != null)
|
if (_dragDropGroup != null && _dragDropOption != null)
|
||||||
|
|
@ -342,6 +347,7 @@ public sealed class ModGroupEditDrawer(
|
||||||
|
|
||||||
_dragDropGroup = null;
|
_dragDropGroup = null;
|
||||||
_dragDropOption = null;
|
_dragDropOption = null;
|
||||||
|
_draggingAcross = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Text;
|
using OtterGui.Text;
|
||||||
using OtterGui.Text.HelperObjects;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -12,79 +10,78 @@ namespace Penumbra.UI.ModsTab;
|
||||||
|
|
||||||
public static class ImcManipulationDrawer
|
public static class ImcManipulationDrawer
|
||||||
{
|
{
|
||||||
public static bool DrawObjectType(ref ImcManipulation manip, float width = 110)
|
public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110)
|
||||||
{
|
{
|
||||||
var ret = Combos.ImcType("##imcType", manip.ObjectType, out var type, width);
|
var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width);
|
||||||
ImUtf8.HoverTooltip("Object Type"u8);
|
ImUtf8.HoverTooltip("Object Type"u8);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
{
|
{
|
||||||
var equipSlot = type switch
|
var equipSlot = type switch
|
||||||
{
|
{
|
||||||
ObjectType.Equipment => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head,
|
ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
|
||||||
ObjectType.DemiHuman => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head,
|
ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head,
|
||||||
ObjectType.Accessory => manip.EquipSlot.IsAccessory() ? manip.EquipSlot : EquipSlot.Ears,
|
ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears,
|
||||||
_ => EquipSlot.Unknown,
|
_ => EquipSlot.Unknown,
|
||||||
};
|
};
|
||||||
manip = new ImcManipulation(type, manip.BodySlot, manip.PrimaryId, manip.SecondaryId == 0 ? 1 : manip.SecondaryId,
|
identifier = identifier with
|
||||||
manip.Variant.Id, equipSlot, manip.Entry);
|
{
|
||||||
|
EquipSlot = equipSlot,
|
||||||
|
SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DrawPrimaryId(ref ImcManipulation manip, float unscaledWidth = 80)
|
public static bool DrawPrimaryId(ref ImcIdentifier identifier, float unscaledWidth = 80)
|
||||||
{
|
{
|
||||||
var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, manip.PrimaryId.Id, out var newId, 0, ushort.MaxValue,
|
var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, identifier.PrimaryId.Id, out var newId, 0, ushort.MaxValue,
|
||||||
manip.PrimaryId.Id <= 1);
|
identifier.PrimaryId.Id <= 1);
|
||||||
ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8
|
ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8
|
||||||
+ "This should generally not be left <= 1 unless you explicitly want that."u8);
|
+ "This should generally not be left <= 1 unless you explicitly want that."u8);
|
||||||
if (ret)
|
if (ret)
|
||||||
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, newId, manip.SecondaryId, manip.Variant.Id, manip.EquipSlot,
|
identifier = identifier with { PrimaryId = newId };
|
||||||
manip.Entry);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DrawSecondaryId(ref ImcManipulation manip, float unscaledWidth = 100)
|
public static bool DrawSecondaryId(ref ImcIdentifier identifier, float unscaledWidth = 100)
|
||||||
{
|
{
|
||||||
var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, manip.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false);
|
var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, identifier.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false);
|
||||||
ImUtf8.HoverTooltip("Secondary ID"u8);
|
ImUtf8.HoverTooltip("Secondary ID"u8);
|
||||||
if (ret)
|
if (ret)
|
||||||
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, newId, manip.Variant.Id, manip.EquipSlot,
|
identifier = identifier with { SecondaryId = newId };
|
||||||
manip.Entry);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DrawVariant(ref ImcManipulation manip, float unscaledWidth = 45)
|
public static bool DrawVariant(ref ImcIdentifier identifier, float unscaledWidth = 45)
|
||||||
{
|
{
|
||||||
var ret = IdInput("##imcVariant"u8, unscaledWidth, manip.Variant.Id, out var newId, 0, byte.MaxValue, false);
|
var ret = IdInput("##imcVariant"u8, unscaledWidth, identifier.Variant.Id, out var newId, 0, byte.MaxValue, false);
|
||||||
ImUtf8.HoverTooltip("Variant ID"u8);
|
ImUtf8.HoverTooltip("Variant ID"u8);
|
||||||
if (ret)
|
if (ret)
|
||||||
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, (byte)newId, manip.EquipSlot,
|
identifier = identifier with { Variant = (byte)newId };
|
||||||
manip.Entry);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DrawSlot(ref ImcManipulation manip, float unscaledWidth = 100)
|
public static bool DrawSlot(ref ImcIdentifier identifier, float unscaledWidth = 100)
|
||||||
{
|
{
|
||||||
bool ret;
|
bool ret;
|
||||||
EquipSlot slot;
|
EquipSlot slot;
|
||||||
switch (manip.ObjectType)
|
switch (identifier.ObjectType)
|
||||||
{
|
{
|
||||||
case ObjectType.Equipment:
|
case ObjectType.Equipment:
|
||||||
case ObjectType.DemiHuman:
|
case ObjectType.DemiHuman:
|
||||||
ret = Combos.EqpEquipSlot("##slot", manip.EquipSlot, out slot, unscaledWidth);
|
ret = Combos.EqpEquipSlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
|
||||||
break;
|
break;
|
||||||
case ObjectType.Accessory:
|
case ObjectType.Accessory:
|
||||||
ret = Combos.AccessorySlot("##slot", manip.EquipSlot, out slot, unscaledWidth);
|
ret = Combos.AccessorySlot("##slot", identifier.EquipSlot, out slot, unscaledWidth);
|
||||||
break;
|
break;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImUtf8.HoverTooltip("Equip Slot"u8);
|
ImUtf8.HoverTooltip("Equip Slot"u8);
|
||||||
if (ret)
|
if (ret)
|
||||||
manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, manip.Variant.Id, slot,
|
identifier = identifier with { EquipSlot = slot };
|
||||||
manip.Entry);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue