Add some functionality to allow an IMC group to add apply to all variants.

This commit is contained in:
Ottermandias 2024-06-02 12:08:49 +02:00
parent 137b752196
commit 05d010a281
10 changed files with 117 additions and 46 deletions

View file

@ -51,7 +51,7 @@ public readonly struct ImcCache : IDisposable
try
{
if (!_imcFiles.TryGetValue(path, out var file))
file = new ImcFile(manager, manip);
file = new ImcFile(manager, manip.Identifier);
_imcManipulations[idx] = (manip, file);
if (!manip.Apply(file))

View file

@ -110,7 +110,7 @@ public partial class TexToolsMeta
var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId,
metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
new ImcEntry());
var def = new ImcFile(_metaFileManager, manip);
var def = new ImcFile(_metaFileManager, manip.Identifier);
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
foreach (var value in values)
{

View file

@ -133,7 +133,7 @@ public partial class TexToolsMeta
{
case MetaManipulation.Type.Imc:
var allManips = manips.ToList();
var baseFile = new ImcFile(manager, allManips[0].Imc);
var baseFile = new ImcFile(manager, allManips[0].Imc.Identifier);
foreach (var manip in allManips)
manip.Imc.Apply(baseFile);

View file

@ -9,20 +9,20 @@ namespace Penumbra.Meta.Files;
public class ImcException : Exception
{
public readonly ImcManipulation Manipulation;
public readonly string GamePath;
public readonly ImcIdentifier Identifier;
public readonly string GamePath;
public ImcException(ImcManipulation manip, Utf8GamePath path)
public ImcException(ImcIdentifier identifier, Utf8GamePath path)
{
Manipulation = manip;
GamePath = path.ToString();
Identifier = identifier;
GamePath = path.ToString();
}
public override string Message
=> "Could not obtain default Imc File.\n"
+ " Either the default file does not exist (possibly for offhand files from TexTools) or the installation is corrupted.\n"
+ $" Game Path: {GamePath}\n"
+ $" Manipulation: {Manipulation}";
+ $" Manipulation: {Identifier}";
}
public unsafe class ImcFile : MetaBaseFile
@ -142,13 +142,14 @@ public unsafe class ImcFile : MetaBaseFile
}
}
public ImcFile(MetaFileManager manager, ImcManipulation manip)
public ImcFile(MetaFileManager manager, ImcIdentifier identifier)
: base(manager, 0)
{
Path = manip.GamePath();
var file = manager.GameData.GetFile(Path.ToString());
var path = identifier.GamePathString();
Path = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty;
var file = manager.GameData.GetFile(path);
if (file == null)
throw new ImcException(manip, Path);
throw new ImcException(identifier, Path);
fixed (byte* ptr = file.Data)
{

View file

@ -4,11 +4,32 @@ using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta;
public class ImcChecker(MetaFileManager metaFileManager)
public class ImcChecker
{
private static readonly Dictionary<ImcIdentifier, int> VariantCounts = [];
private static MetaFileManager? _dataManager;
public static int GetVariantCount(ImcIdentifier identifier)
{
if (VariantCounts.TryGetValue(identifier, out var count))
return count;
count = GetFile(identifier)?.Count ?? 0;
VariantCounts[identifier] = count;
return count;
}
public readonly record struct CachedEntry(ImcEntry Entry, bool FileExists, bool VariantExists);
private readonly Dictionary<ImcIdentifier, CachedEntry> _cachedDefaultEntries = new();
private readonly MetaFileManager _metaFileManager;
public ImcChecker(MetaFileManager metaFileManager)
{
_metaFileManager = metaFileManager;
_dataManager = metaFileManager;
}
public CachedEntry GetDefaultEntry(ImcIdentifier identifier, bool storeCache)
{
@ -17,7 +38,7 @@ public class ImcChecker(MetaFileManager metaFileManager)
try
{
var e = ImcFile.GetDefault(metaFileManager, identifier.GamePath(), identifier.EquipSlot, identifier.Variant, out var entryExists);
var e = ImcFile.GetDefault(_metaFileManager, identifier.GamePath(), identifier.EquipSlot, identifier.Variant, out var entryExists);
entry = new CachedEntry(e, true, entryExists);
}
catch (Exception)
@ -33,4 +54,19 @@ public class ImcChecker(MetaFileManager metaFileManager)
public CachedEntry GetDefaultEntry(ImcManipulation imcManip, bool storeCache)
=> GetDefaultEntry(new ImcIdentifier(imcManip.PrimaryId, imcManip.Variant, imcManip.ObjectType, imcManip.SecondaryId.Id,
imcManip.EquipSlot, imcManip.BodySlot), storeCache);
private static ImcFile? GetFile(ImcIdentifier identifier)
{
if (_dataManager == null)
return null;
try
{
return new ImcFile(_dataManager, identifier);
}
catch
{
return null;
}
}
}

View file

@ -31,12 +31,16 @@ public readonly record struct ImcIdentifier(
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId.Id, Variant.Id, EquipSlot, entry);
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> AddChangedItems(identifier, changedItems, false);
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems, bool allVariants)
{
var path = ObjectType switch
{
ObjectType.Equipment or ObjectType.Accessory => GamePaths.Equipment.Mtrl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot,
Variant,
"a"),
ObjectType.Equipment when allVariants => GamePaths.Equipment.Mdl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot),
ObjectType.Equipment => GamePaths.Equipment.Mtrl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"),
ObjectType.Accessory when allVariants => GamePaths.Accessory.Mdl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot),
ObjectType.Accessory => GamePaths.Accessory.Mtrl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"),
ObjectType.Weapon => GamePaths.Weapon.Mtrl.Path(PrimaryId, SecondaryId.Id, Variant, "a"),
ObjectType.DemiHuman => GamePaths.DemiHuman.Mtrl.Path(PrimaryId, SecondaryId.Id, EquipSlot, Variant,
"a"),
@ -49,24 +53,19 @@ public readonly record struct ImcIdentifier(
identifier.Identify(changedItems, path);
}
public Utf8GamePath GamePath()
{
return ObjectType switch
public string GamePathString()
=> 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.Id), out var p)
? p
: Utf8GamePath.Empty,
ObjectType.Monster => Utf8GamePath.FromString(GamePaths.Monster.Imc.Path(PrimaryId, SecondaryId.Id), out var p)
? p
: Utf8GamePath.Empty,
ObjectType.Weapon => Utf8GamePath.FromString(GamePaths.Weapon.Imc.Path(PrimaryId, SecondaryId.Id), out var p)
? p
: Utf8GamePath.Empty,
_ => throw new NotImplementedException(),
ObjectType.Accessory => GamePaths.Accessory.Imc.Path(PrimaryId),
ObjectType.Equipment => GamePaths.Equipment.Imc.Path(PrimaryId),
ObjectType.DemiHuman => GamePaths.DemiHuman.Imc.Path(PrimaryId, SecondaryId.Id),
ObjectType.Monster => GamePaths.Monster.Imc.Path(PrimaryId, SecondaryId.Id),
ObjectType.Weapon => GamePaths.Weapon.Imc.Path(PrimaryId, SecondaryId.Id),
_ => string.Empty,
};
}
public Utf8GamePath GamePath()
=> Utf8GamePath.FromString(GamePathString(), out var p) ? p : Utf8GamePath.Empty;
public MetaIndex FileIndex()
=> (MetaIndex)(-1);

View file

@ -6,6 +6,7 @@ using OtterGui.Classes;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
@ -31,6 +32,8 @@ public class ImcModGroup(Mod mod) : IModGroup
public ImcIdentifier Identifier;
public ImcEntry DefaultEntry;
public bool AllVariants;
public FullPath? FindBestMatch(Utf8GamePath gamePath)
=> null;
@ -39,7 +42,7 @@ public class ImcModGroup(Mod mod) : IModGroup
public bool CanBeDisabled
{
get => OptionData.Any(m => m.IsDisableSubMod);
get => _canBeDisabled;
set
{
_canBeDisabled = value;
@ -92,8 +95,8 @@ public class ImcModGroup(Mod mod) : IModGroup
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
=> new ImcModGroupEditDrawer(editDrawer, this);
public ImcManipulation GetManip(ushort mask)
=> new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, Identifier.Variant.Id,
public ImcManipulation GetManip(ushort mask, Variant variant)
=> new(Identifier.ObjectType, Identifier.BodySlot, Identifier.PrimaryId, Identifier.SecondaryId.Id, variant.Id,
Identifier.EquipSlot, DefaultEntry with { AttributeMask = mask });
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations)
@ -102,12 +105,23 @@ public class ImcModGroup(Mod mod) : IModGroup
return;
var mask = GetCurrentMask(setting);
var imc = GetManip(mask);
manipulations.Add(imc);
if (AllVariants)
{
var count = ImcChecker.GetVariantCount(Identifier);
if (count == 0)
manipulations.Add(GetManip(mask, Identifier.Variant));
else
for (var i = 0; i <= count; ++i)
manipulations.Add(GetManip(mask, (Variant)i));
}
else
{
manipulations.Add(GetManip(mask, Identifier.Variant));
}
}
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
=> Identifier.AddChangedItems(identifier, changedItems);
=> Identifier.AddChangedItems(identifier, changedItems, AllVariants);
public Setting FixSetting(Setting setting)
=> new(setting.Value & ((1ul << OptionData.Count) - 1));
@ -120,6 +134,8 @@ public class ImcModGroup(Mod mod) : IModGroup
jObj.WriteTo(jWriter);
jWriter.WritePropertyName(nameof(DefaultEntry));
serializer.Serialize(jWriter, DefaultEntry);
jWriter.WritePropertyName(nameof(AllVariants));
jWriter.WriteValue(AllVariants);
jWriter.WritePropertyName("Options");
jWriter.WriteStartArray();
foreach (var option in OptionData)
@ -156,6 +172,7 @@ public class ImcModGroup(Mod mod) : IModGroup
Description = json[nameof(Description)]?.ToObject<string>() ?? string.Empty,
Priority = json[nameof(Priority)]?.ToObject<ModPriority>() ?? ModPriority.Default,
DefaultEntry = json[nameof(DefaultEntry)]?.ToObject<ImcEntry>() ?? new ImcEntry(),
AllVariants = json[nameof(AllVariants)]?.ToObject<bool>() ?? false,
};
if (ret.Name.Length == 0)
return null;
@ -210,7 +227,7 @@ public class ImcModGroup(Mod mod) : IModGroup
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.");
Penumbra.Log.Warning("A IMC Group should be able to be disabled, but does not contain a disable option.");
return false;
}

View file

@ -51,7 +51,7 @@ public static class EquipmentSwap
var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom);
var imcManip = new ImcManipulation(slotTo, variantTo.Id, idTo.Id, default);
var imcFileTo = new ImcFile(manager, imcManip);
var imcFileTo = new ImcFile(manager, imcManip.Identifier);
var skipFemale = false;
var skipMale = false;
var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo.Id))).Imc.Entry.MaterialId;
@ -121,7 +121,7 @@ public static class EquipmentSwap
{
(var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom);
var imcManip = new ImcManipulation(slot, variantTo.Id, idTo, default);
var imcFileTo = new ImcFile(manager, imcManip);
var imcFileTo = new ImcFile(manager, imcManip.Identifier);
var isAccessory = slot.IsAccessory();
var estType = slot switch
@ -250,7 +250,7 @@ public static class EquipmentSwap
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom)
{
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
var imc = new ImcFile(manager, entry);
var imc = new ImcFile(manager, entry.Identifier);
EquipItem[] items;
Variant[] variants;
if (idFrom == idTo)

View file

@ -2,6 +2,7 @@ using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Services;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
@ -14,7 +15,8 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
: ModOptionEditor<ImcModGroup, ImcSubMod>(communicator, saveService, config), IService
{
/// <summary> Add a new, empty imc group with the given manipulation data. </summary>
public ImcModGroup? AddModGroup(Mod mod, string newName, ImcIdentifier identifier, ImcEntry defaultEntry, 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))
return null;
@ -78,6 +80,16 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, option.Mod, option.Group, option, null, -1);
}
public void ChangeAllVariants(ImcModGroup group, bool allVariants, SaveType saveType = SaveType.Queue)
{
if (group.AllVariants == allVariants)
return;
group.AllVariants = allVariants;
SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport));
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, group.Mod, group, null, null, -1);
}
public void ChangeCanBeDisabled(ImcModGroup group, bool canBeDisabled, SaveType saveType = SaveType.Queue)
{
if (group.CanBeDisabled == canBeDisabled)

View file

@ -19,7 +19,13 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
var entry = group.DefaultEntry;
var changes = false;
ImUtf8.TextFramed(identifier.ToString(), 0, editor.AvailableWidth, borderColor: ImGui.GetColorU32(ImGuiCol.Border));
var width = editor.AvailableWidth.X - ImUtf8.ItemInnerSpacing.X - ImUtf8.CalcTextSize("All Variants"u8).X;
ImUtf8.TextFramed(identifier.ToString(), 0, new Vector2(width, 0), borderColor: ImGui.GetColorU32(ImGuiCol.Border));
ImUtf8.SameLineInner();
var allVariants = group.AllVariants;
if (ImUtf8.Checkbox("All Variants"u8, ref allVariants))
editor.ModManager.OptionEditor.ImcEditor.ChangeAllVariants(group, allVariants);
ImUtf8.HoverTooltip("Make this group overwrite all corresponding variants for this identifier, not just the one specified."u8);
using (ImUtf8.Group())
{