Add the option to omit mch offhands from changed items.

This commit is contained in:
Ottermandias 2024-05-21 18:24:21 +02:00
parent bb56faa288
commit e85b84dafe
13 changed files with 192 additions and 117 deletions

View file

@ -6,6 +6,7 @@ using Penumbra.Communication;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes;
using Penumbra.Mods.Manager;
using Penumbra.Util;
namespace Penumbra.Collections.Cache;
@ -439,9 +440,12 @@ public sealed class CollectionCache : IDisposable
foreach (var (manip, mod) in Meta)
{
ModCacheManager.ComputeChangedItems(identifier, items, manip);
identifier.MetaChangedItems(items, manip);
AddItems(mod);
}
if (_manager.Config.HideMachinistOffhandFromChangedItems)
_changedItems.RemoveMachinistOffhands();
}
catch (Exception e)
{

View file

@ -25,6 +25,7 @@ public class CollectionCacheManager : IDisposable
private readonly ModStorage _modStorage;
private readonly CollectionStorage _storage;
private readonly ActiveCollections _active;
internal readonly Configuration Config;
internal readonly ResolvedFileChanged ResolvedFileChanged;
internal readonly MetaFileManager MetaFileManager;
internal readonly ResourceLoader ResourceLoader;
@ -40,7 +41,8 @@ public class CollectionCacheManager : IDisposable
=> _storage.Where(c => c.HasCache);
public CollectionCacheManager(FrameworkManager framework, CommunicatorService communicator, TempModManager tempMods, ModStorage modStorage,
MetaFileManager metaFileManager, ActiveCollections active, CollectionStorage storage, ResourceLoader resourceLoader)
MetaFileManager metaFileManager, ActiveCollections active, CollectionStorage storage, ResourceLoader resourceLoader,
Configuration config)
{
_framework = framework;
_communicator = communicator;
@ -50,6 +52,7 @@ public class CollectionCacheManager : IDisposable
_active = active;
_storage = storage;
ResourceLoader = resourceLoader;
Config = config;
ResolvedFileChanged = _communicator.ResolvedFileChanged;
if (!_active.Individuals.IsLoaded)
@ -260,7 +263,8 @@ public class CollectionCacheManager : IDisposable
}
/// <summary> Prepare Changes by removing mods from caches with collections or add or reload mods. </summary>
private void OnModOptionChange(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container, int movedToIdx)
private void OnModOptionChange(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
int movedToIdx)
{
if (type is ModOptionChangeType.PrepareChange)
{

View file

@ -51,6 +51,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool ReplaceNonAsciiOnImport { get; set; } = false;
public bool HidePrioritiesInSelector { get; set; } = false;
public bool HideRedrawBar { get; set; } = false;
public bool HideMachinistOffhandFromChangedItems { get; set; } = true;
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
public int OptionGroupCollapsibleMin { get; set; } = 5;

View file

@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
@ -40,6 +41,7 @@ public interface IModGroup
public int GetIndex();
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, HashSet<MetaManipulation> manipulations);
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems);
/// <summary> Ensure that a value is valid for a group. </summary>
public Setting FixSetting(Setting setting);

View file

@ -3,12 +3,14 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods.Groups;
@ -119,6 +121,9 @@ public class ImcModGroup(Mod mod) : IModGroup
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)));

View file

@ -4,10 +4,12 @@ using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Classes;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods.Groups;
@ -114,6 +116,12 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup
}
}
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{
foreach (var container in DataContainers)
identifier.AddChangedItems(container, changedItems);
}
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
{
ModSaveGroup.WriteJsonBase(jWriter, this);

View file

@ -2,10 +2,12 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods.Groups;
@ -99,6 +101,12 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup
OptionData[setting.AsIndex].AddDataTo(redirections, manipulations);
}
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems)
{
foreach (var container in DataContainers)
identifier.AddChangedItems(container, changedItems);
}
public Setting FixSetting(Setting setting)
=> OptionData.Count == 0 ? Setting.Zero : new Setting(Math.Min(setting.Value, (ulong)(OptionData.Count - 1)));

View file

@ -1,26 +1,27 @@
using Penumbra.Communication;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Mods.Manager;
public class ModCacheManager : IDisposable
{
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly ObjectIdentification _identifier;
private readonly ModStorage _modManager;
private bool _updatingItems = false;
public ModCacheManager(CommunicatorService communicator, ObjectIdentification identifier, ModStorage modStorage)
public ModCacheManager(CommunicatorService communicator, ObjectIdentification identifier, ModStorage modStorage, Configuration config)
{
_communicator = communicator;
_identifier = identifier;
_modManager = modStorage;
_config = config;
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.ModCacheManager);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModCacheManager);
@ -38,75 +39,8 @@ public class ModCacheManager : IDisposable
_communicator.ModDiscoveryFinished.Unsubscribe(OnModDiscoveryFinished);
}
/// <summary> Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. </summary>
public static void ComputeChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems, MetaManipulation manip)
{
switch (manip.ManipulationType)
{
case MetaManipulation.Type.Imc:
switch (manip.Imc.ObjectType)
{
case ObjectType.Equipment:
case ObjectType.Accessory:
identifier.Identify(changedItems,
GamePaths.Equipment.Mtrl.Path(manip.Imc.PrimaryId, GenderRace.MidlanderMale, manip.Imc.EquipSlot, manip.Imc.Variant,
"a"));
break;
case ObjectType.Weapon:
identifier.Identify(changedItems,
GamePaths.Weapon.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
break;
case ObjectType.DemiHuman:
identifier.Identify(changedItems,
GamePaths.DemiHuman.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.EquipSlot, manip.Imc.Variant,
"a"));
break;
case ObjectType.Monster:
identifier.Identify(changedItems,
GamePaths.Monster.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
break;
}
break;
case MetaManipulation.Type.Eqdp:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Eqdp.SetId, Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race), manip.Eqdp.Slot));
break;
case MetaManipulation.Type.Eqp:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Eqp.SetId, GenderRace.MidlanderMale, manip.Eqp.Slot));
break;
case MetaManipulation.Type.Est:
switch (manip.Est.Slot)
{
case EstManipulation.EstType.Hair:
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null);
break;
case EstManipulation.EstType.Face:
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null);
break;
case EstManipulation.EstType.Body:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
EquipSlot.Body));
break;
case EstManipulation.EstType.Head:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
EquipSlot.Head));
break;
}
break;
case MetaManipulation.Type.Gmp:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Gmp.SetId, GenderRace.MidlanderMale, EquipSlot.Head));
break;
case MetaManipulation.Type.Rsp:
changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null);
break;
}
}
private void OnModOptionChange(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container, int fromIdx)
private void OnModOptionChange(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
int fromIdx)
{
switch (type)
{
@ -194,16 +128,14 @@ public class ModCacheManager : IDisposable
private void UpdateChangedItems(Mod mod)
{
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
changedItems.Clear();
foreach (var gamePath in mod.AllDataContainers.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
_identifier.Identify(changedItems, gamePath.ToString());
mod.ChangedItems.Clear();
foreach (var manip in mod.AllDataContainers.SelectMany(m => m.Manipulations))
ComputeChangedItems(_identifier, changedItems, manip);
_identifier.AddChangedItems(mod.Default, mod.ChangedItems);
foreach (var group in mod.Groups)
group.AddChangedItems(_identifier, mod.ChangedItems);
foreach(var imcGroup in mod.Groups.OfType<ImcModGroup>())
ComputeChangedItems(_identifier, changedItems, imcGroup.GetManip(0));
if (_config.HideMachinistOffhandFromChangedItems)
mod.ChangedItems.RemoveMachinistOffhands();
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
}

View file

@ -5,12 +5,8 @@ using Penumbra.Mods.Editor;
namespace Penumbra.Mods.Manager;
public class ModImportManager : IDisposable
public class ModImportManager(ModManager modManager, Configuration config, ModEditor modEditor) : IDisposable
{
private readonly ModManager _modManager;
private readonly Configuration _config;
private readonly ModEditor _modEditor;
private readonly ConcurrentQueue<string[]> _modsToUnpack = new();
/// <summary> Mods need to be added thread-safely outside of iteration. </summary>
@ -26,13 +22,6 @@ public class ModImportManager : IDisposable
=> _modsToAdd;
public ModImportManager(ModManager modManager, Configuration config, ModEditor modEditor)
{
_modManager = modManager;
_config = config;
_modEditor = modEditor;
}
public void TryUnpacking()
{
if (Importing || !_modsToUnpack.TryDequeue(out var newMods))
@ -51,7 +40,7 @@ public class ModImportManager : IDisposable
if (files.Length == 0)
return;
_import = new TexToolsImporter(files.Length, files, AddNewMod, _config, _modEditor, _modManager, _modEditor.Compactor);
_import = new TexToolsImporter(files.Length, files, AddNewMod, config, modEditor, modManager, modEditor.Compactor);
}
public bool Importing
@ -87,8 +76,8 @@ public class ModImportManager : IDisposable
return false;
}
_modManager.AddMod(directory);
mod = _modManager.LastOrDefault();
modManager.AddMod(directory);
mod = modManager.LastOrDefault();
return mod != null && mod.ModPath == directory;
}

View file

@ -99,7 +99,7 @@ public sealed class Mod : IMod
}
// Cache
public readonly IReadOnlyDictionary<string, object?> ChangedItems = new SortedList<string, object?>();
public readonly SortedList<string, object?> ChangedItems = new();
public string LowerChangedItemsString { get; internal set; } = string.Empty;
public string AllTagsLower { get; internal set; } = string.Empty;

View file

@ -123,8 +123,7 @@ public sealed class ModGroupEditDrawer(
ModManager modManager,
Configuration config,
FilenameService filenames,
DescriptionEditPopup descriptionPopup,
MetaFileManager metaManager) : IUiService
DescriptionEditPopup descriptionPopup) : IUiService
{
private static ReadOnlySpan<byte> DragDropLabel
=> "##DragOption"u8;

View file

@ -428,6 +428,14 @@ public class SettingsTab : ITab
_config.Ephemeral.Save();
}
});
Checkbox("Omit Machinist Offhands in Changed Items",
"Omits all Aetherotransformers (machinist offhands) in the changed items tabs because any change on them changes all of them at the moment.\n\n"
+ "Changing this triggers a rediscovery of your mods so all changed items can be updated.",
_config.HideMachinistOffhandFromChangedItems, v =>
{
_config.HideMachinistOffhandFromChangedItems = v;
_modManager.DiscoverMods();
});
Checkbox("Hide Priority Numbers in Mod Selector",
"Hides the bracketed non-zero priority numbers displayed in the mod selector when there is enough space for them.",
_config.HidePrioritiesInSelector, v => _config.HidePrioritiesInSelector = v);

View file

@ -0,0 +1,115 @@
using OtterGui.Classes;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.SubMods;
namespace Penumbra.Util;
public static class IdentifierExtensions
{
/// <summary> Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. </summary>
public static void MetaChangedItems(this ObjectIdentification identifier, IDictionary<string, object?> changedItems,
MetaManipulation manip)
{
switch (manip.ManipulationType)
{
case MetaManipulation.Type.Imc:
switch (manip.Imc.ObjectType)
{
case ObjectType.Equipment:
case ObjectType.Accessory:
identifier.Identify(changedItems,
GamePaths.Equipment.Mtrl.Path(manip.Imc.PrimaryId, GenderRace.MidlanderMale, manip.Imc.EquipSlot, manip.Imc.Variant,
"a"));
break;
case ObjectType.Weapon:
identifier.Identify(changedItems,
GamePaths.Weapon.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
break;
case ObjectType.DemiHuman:
identifier.Identify(changedItems,
GamePaths.DemiHuman.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.EquipSlot, manip.Imc.Variant,
"a"));
break;
case ObjectType.Monster:
identifier.Identify(changedItems,
GamePaths.Monster.Mtrl.Path(manip.Imc.PrimaryId, manip.Imc.SecondaryId, manip.Imc.Variant, "a"));
break;
}
break;
case MetaManipulation.Type.Eqdp:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Eqdp.SetId, Names.CombinedRace(manip.Eqdp.Gender, manip.Eqdp.Race), manip.Eqdp.Slot));
break;
case MetaManipulation.Type.Eqp:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Eqp.SetId, GenderRace.MidlanderMale, manip.Eqp.Slot));
break;
case MetaManipulation.Type.Est:
switch (manip.Est.Slot)
{
case EstManipulation.EstType.Hair:
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Hair (Hair) {manip.Est.SetId}", null);
break;
case EstManipulation.EstType.Face:
changedItems.TryAdd($"Customization: {manip.Est.Race} {manip.Est.Gender} Face (Face) {manip.Est.SetId}", null);
break;
case EstManipulation.EstType.Body:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
EquipSlot.Body));
break;
case EstManipulation.EstType.Head:
identifier.Identify(changedItems,
GamePaths.Equipment.Mdl.Path(manip.Est.SetId, Names.CombinedRace(manip.Est.Gender, manip.Est.Race),
EquipSlot.Head));
break;
}
break;
case MetaManipulation.Type.Gmp:
identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(manip.Gmp.SetId, GenderRace.MidlanderMale, EquipSlot.Head));
break;
case MetaManipulation.Type.Rsp:
changedItems.TryAdd($"{manip.Rsp.SubRace.ToName()} {manip.Rsp.Attribute.ToFullString()}", null);
break;
}
}
public static void AddChangedItems(this ObjectIdentification identifier, IModDataContainer container,
IDictionary<string, object?> changedItems)
{
foreach (var gamePath in container.Files.Keys.Concat(container.FileSwaps.Keys))
identifier.Identify(changedItems, gamePath.ToString());
foreach (var manip in container.Manipulations)
MetaChangedItems(identifier, changedItems, manip);
}
public static void RemoveMachinistOffhands(this SortedList<string, object?> changedItems)
{
for (var i = 0; i < changedItems.Count; i++)
{
{
var value = changedItems.Values[i];
if (value is EquipItem { Type: FullEquipType.GunOff })
changedItems.RemoveAt(i--);
}
}
}
public static void RemoveMachinistOffhands(this SortedList<string, (SingleArray<IMod>, object?)> changedItems)
{
for (var i = 0; i < changedItems.Count; i++)
{
{
var value = changedItems.Values[i].Item2;
if (value is EquipItem { Type: FullEquipType.GunOff })
changedItems.RemoveAt(i--);
}
}
}
}