From d56c2db547e64496e794e68ee1771c49096b0f58 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:32:23 +0100 Subject: [PATCH] Add more mod association and modded utility. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 - .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 54 ++++---- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 16 ++- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 55 +++++++- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 105 ++++++++++++-- Glamourer/Interop/Penumbra/PenumbraService.cs | 130 +++++++++++------- OtterGui | 2 +- Penumbra.Api | 2 +- 9 files changed, 277 insertions(+), 90 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index dd198ae..748afea 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -17,7 +17,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; -using OtterGuiInternal.Structs; using Penumbra.GameData.Enums; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 021a396..feff657 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -15,7 +15,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager, Configuration config) { - private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); + private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log, selector); private (Mod, ModSettings)[]? _copy; public void Draw() diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 53501b0..6ec9a1c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -4,19 +4,18 @@ using ImGuiNET; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> +public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings, int Count)> { - public ModCombo(PenumbraService penumbra, Logger log) - : base(penumbra.GetMods, MouseWheelType.None, log) - { - SearchByParts = false; - } + public ModCombo(PenumbraService penumbra, Logger log, DesignFileSystemSelector selector) + : base(() => penumbra.GetMods(selector.Selected?.FilteredItemNames.ToArray() ?? []), MouseWheelType.None, log) + => SearchByParts = false; - protected override string ToString((Mod Mod, ModSettings Settings) obj) + protected override string ToString((Mod Mod, ModSettings Settings, int Count) obj) => obj.Mod.Name; protected override bool IsVisible(int globalIndex, LowerString filter) @@ -24,36 +23,45 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> protected override bool DrawSelectable(int globalIdx, bool selected) { - using var id = ImRaii.PushId(globalIdx); - var (mod, settings) = Items[globalIdx]; + using var id = ImUtf8.PushId(globalIdx); + var (mod, settings, count) = Items[globalIdx]; bool ret; - using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !settings.Enabled)) + var color = settings.Enabled + ? count > 0 + ? ColorId.ContainsItemsEnabled.Value() + : ImGui.GetColorU32(ImGuiCol.Text) + : count > 0 + ? ColorId.ContainsItemsDisabled.Value() + : ImGui.GetColorU32(ImGuiCol.TextDisabled); + using (ImRaii.PushColor(ImGuiCol.Text, color)) { - ret = ImGui.Selectable(mod.Name, selected); + ret = ImUtf8.Selectable(mod.Name, selected); } if (ImGui.IsItemHovered()) { using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); - using var tt = ImRaii.Tooltip(); + using var tt = ImUtf8.Tooltip(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); - using (var group = ImRaii.Group()) + using (ImUtf8.Group()) { if (namesDifferent) - ImGui.TextUnformatted("Directory Name"); - ImGui.TextUnformatted("Enabled"); - ImGui.TextUnformatted("Priority"); + ImUtf8.Text("Directory Name"u8); + ImUtf8.Text("Enabled"u8); + ImUtf8.Text("Priority"u8); + ImUtf8.Text("Affected Design Items"u8); DrawSettingsLeft(settings); } ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * ImGui.GetStyle().ItemSpacing.X, 150 * ImGuiHelpers.GlobalScale)); - using (var group = ImRaii.Group()) + using (ImUtf8.Group()) { if (namesDifferent) - ImGui.TextUnformatted(mod.DirectoryName); - ImGui.TextUnformatted(settings.Enabled.ToString()); - ImGui.TextUnformatted(settings.Priority.ToString()); + ImUtf8.Text(mod.DirectoryName); + ImUtf8.Text($"{settings.Enabled}"); + ImUtf8.Text($"{settings.Priority}"); + ImUtf8.Text($"{count}"); DrawSettingsRight(settings); } } @@ -65,7 +73,7 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> { foreach (var setting in settings.Settings) { - ImGui.TextUnformatted(setting.Key); + ImUtf8.Text(setting.Key); for (var i = 1; i < setting.Value.Count; ++i) ImGui.NewLine(); } @@ -76,10 +84,10 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> foreach (var setting in settings.Settings) { if (setting.Value.Count == 0) - ImGui.TextUnformatted(""); + ImUtf8.Text(""u8); else foreach (var option in setting.Value) - ImGui.TextUnformatted(option); + ImUtf8.Text(option); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 9e894c4..a7afa21 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -5,11 +5,15 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; +using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors) +public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, Configuration config) { + private readonly Button[] _leftButtons = []; + private readonly Button[] _rightButtons = [new IncognitoButton(config.Ephemeral)]; + private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() @@ -17,8 +21,12 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e if (selector.SelectedPaths.Count == 0) return; - var width = ImGuiHelpers.ScaledVector2(145, 0); - ImGui.NewLine(); + HeaderDrawer.Draw(string.Empty, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), _leftButtons, _rightButtons); + using var child = ImUtf8.Child("##MultiPanel"u8, default, true); + if (!child) + return; + + var width = ImGuiHelpers.ScaledVector2(145, 0); var treeNodePos = ImGui.GetCursorPos(); _numDesigns = DrawDesignList(); DrawCounts(treeNodePos); @@ -135,7 +143,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e { ImUtf8.TextFrameAligned("Multi Tagger:"u8); ImGui.SameLine(); - var offset = ImGui.GetItemRectSize().X; + var offset = ImGui.GetItemRectSize().X + ImGui.GetStyle().WindowPadding.X; ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index afc4f7b..903257b 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -1,8 +1,8 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; -using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -23,7 +23,8 @@ public class UnlockOverview( TextureService textures, CodeService codes, JobService jobs, - FavoriteManager favorites) + FavoriteManager favorites, + PenumbraService penumbra) { private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); @@ -32,6 +33,9 @@ public class UnlockOverview( private Gender _selected3 = Gender.Unknown; private BonusItemFlag _selected4 = BonusItemFlag.Unknown; + private uint _favoriteColor; + private uint _moddedColor; + private void DrawSelector() { using var child = ImRaii.Child("Selector", new Vector2(200 * ImGuiHelpers.GlobalScale, -1), true); @@ -90,6 +94,9 @@ public class UnlockOverview( if (!child) return; + _moddedColor = ColorId.ModdedItemMarker.Value(); + _favoriteColor = ColorId.FavoriteStarOn.Value(); + if (_selected1 is not FullEquipType.Unknown) DrawItems(); else if (_selected2 is not SubRace.Unknown && _selected3 is not Gender.Unknown) @@ -120,7 +127,7 @@ public class UnlockOverview( unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) - ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), _favoriteColor, 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); if (hasIcon && ImGui.IsItemHovered()) @@ -192,9 +199,11 @@ public class UnlockOverview( ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(item)) - ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), _favoriteColor, 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + var mods = DrawModdedMarker(item, iconSize); + // TODO handle clicking if (ImGui.IsItemHovered()) { @@ -206,9 +215,10 @@ public class UnlockOverview( ImUtf8.Text($"{item.Id.Id}"); ImUtf8.Text($"{item.PrimaryId.Id}-{item.Variant.Id}"); // TODO - ImUtf8.Text("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); + ImUtf8.Text("Always Unlocked"u8); // : $"Unlocked on {time:g}" : "Not Unlocked."); // TODO //tooltip.CreateTooltip(item, string.Empty, false); + DrawModTooltip(mods); } } } @@ -263,6 +273,8 @@ public class UnlockOverview( ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + var mods = DrawModdedMarker(item, iconSize); + if (ImGui.IsItemClicked()) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); @@ -306,6 +318,7 @@ public class UnlockOverview( ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) ImGui.TextUnformatted("Can apply Crest"); + DrawModTooltip(mods); tooltip.CreateTooltip(item, string.Empty, false); } } @@ -316,4 +329,36 @@ public class UnlockOverview( private static int IconsPerRow(float iconWidth, float iconSpacing) => (int)(ImGui.GetContentRegionAvail().X / (iconWidth + iconSpacing)); + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private (string ModDirectory, string ModName)[] DrawModdedMarker(in EquipItem item, Vector2 iconSize) + { + var mods = penumbra.CheckCurrentChangedItem(item.Name); + if (mods.Length == 0) + return mods; + + var center = ImGui.GetItemRectMin() + new Vector2(iconSize.X * 0.85f, iconSize.Y * 0.15f); + ImGui.GetWindowDrawList().AddCircleFilled(center, iconSize.X * 0.1f, _moddedColor); + ImGui.GetWindowDrawList().AddCircle(center, iconSize.X * 0.1f, 0xFF000000); + return mods; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private void DrawModTooltip((string ModDirectory, string ModName)[] mods) + { + switch (mods.Length) + { + case 0: return; + case 1: + ImUtf8.Text("Modded by: "u8, _moddedColor); + ImGui.SameLine(0, 0); + ImUtf8.Text(mods[0].ModName); + return; + default: + ImUtf8.Text("Modded by:"u8, _moddedColor); + foreach (var (_, mod) in mods) + ImUtf8.BulletText(mod); + return; + } + } } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 9651e85..6d9ce51 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -3,6 +3,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -17,12 +18,16 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public class UnlockTable : Table, IDisposable { - private readonly ObjectUnlocked _event; + private readonly ObjectUnlocked _event; + private readonly PenumbraService _penumbra; + + private Guid _lastCurrentCollection = Guid.Empty; public UnlockTable(ItemManager items, TextureService textures, ItemUnlockManager itemUnlocks, - PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs, FavoriteManager favorites) + PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs, FavoriteManager favorites, PenumbraService penumbra) : base("ItemUnlockTable", new ItemList(items), new FavoriteColumn(favorites, @event) { Label = "F" }, + new ModdedColumn(penumbra) { Label = "M" }, new NameColumn(textures, tooltip) { Label = "Item Name..." }, new SlotColumn { Label = "Equip Slot" }, new TypeColumn { Label = "Item Type..." }, @@ -36,14 +41,40 @@ public class UnlockTable : Table, IDisposable new TradableColumn { Label = "Trade" } ) { - _event = @event; - Sortable = true; - Flags |= ImGuiTableFlags.Hideable | ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable; + _event = @event; + _penumbra = penumbra; + Sortable = true; + Flags |= ImGuiTableFlags.Hideable | ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable; _event.Subscribe(OnObjectUnlock, ObjectUnlocked.Priority.UnlockTable); + _penumbra.ModSettingChanged += OnModSettingsChanged; + + } + + private void OnModSettingsChanged(Penumbra.Api.Enums.ModSettingChange type, Guid collection, string mod, bool inherited) + { + if (collection != _lastCurrentCollection) + return; + + FilterDirty = true; + SortDirty = true; + } + + protected override void PreDraw() + { + var lastCurrentCollection = _penumbra.CurrentCollection.Id; + if (_lastCurrentCollection != lastCurrentCollection) + { + _lastCurrentCollection = lastCurrentCollection; + FilterDirty = true; + SortDirty = true; + } } public void Dispose() - => _event.Unsubscribe(OnObjectUnlock); + { + _event.Unsubscribe(OnObjectUnlock); + _penumbra.ModSettingChanged -= OnModSettingsChanged; + } private sealed class FavoriteColumn : YesNoColumn { @@ -77,6 +108,66 @@ public class UnlockTable : Table, IDisposable => _favorites.Contains(rhs).CompareTo(_favorites.Contains(lhs)); } + private sealed class ModdedColumn : YesNoColumn + { + public override float Width + => ImGui.GetFrameHeightWithSpacing(); + + private readonly PenumbraService _penumbra; + private readonly Dictionary _compareCache = []; + + public ModdedColumn(PenumbraService penumbra) + { + _penumbra = penumbra; + Flags |= ImGuiTableColumnFlags.NoResize; + } + + public override void PostSort() + { + _compareCache.Clear(); + } + + public override void DrawColumn(EquipItem item, int idx) + { + var value = _penumbra.CheckCurrentChangedItem(item.Name); + if (value.Length == 0) + return; + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ModdedItemMarker.Value()); + ImGuiUtil.Center(FontAwesomeIcon.Circle.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using var tt = ImUtf8.Tooltip(); + foreach (var (_, mod) in value) + ImUtf8.BulletText(mod); + } + } + + public override bool FilterFunc(EquipItem item) + => FilterValue.HasFlag(_penumbra.CheckCurrentChangedItem(item.Name).Length > 0 ? YesNoFlag.Yes : YesNoFlag.No); + + public override int Compare(EquipItem lhs, EquipItem rhs) + { + if (!_compareCache.TryGetValue(lhs.Id, out var lhsCount)) + { + lhsCount = _penumbra.CheckCurrentChangedItem(lhs.Name).Length; + _compareCache[lhs.Id] = lhsCount; + } + + if (!_compareCache.TryGetValue(rhs.Id, out var rhsCount)) + { + rhsCount = _penumbra.CheckCurrentChangedItem(rhs.Name).Length; + _compareCache[rhs.Id] = rhsCount; + } + + return lhsCount.CompareTo(rhsCount); + } + } + private sealed class NameColumn : ColumnString { private readonly TextureService _textures; @@ -317,7 +408,6 @@ public class UnlockTable : Table, IDisposable { } } - private sealed class JobColumn : ColumnFlags { public override float Width @@ -415,7 +505,6 @@ public class UnlockTable : Table, IDisposable } } - private sealed class DyableColumn : ColumnFlags { [Flags] diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 27446ea..e0c445b 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -38,6 +38,7 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp = 4; public const int RequiredPenumbraFeatureVersionTemp2 = 5; public const int RequiredPenumbraFeatureVersionTemp3 = 6; + public const int RequiredPenumbraFeatureVersionTemp4 = 7; private const int Key = -1610; @@ -49,30 +50,33 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; - private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; - private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; - private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; - private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; - private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; - private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; - private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; - private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; - private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp? _getCurrentSettingsWithTemp; - private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; - private global::Penumbra.Api.IpcSubscribers.GetAllModSettings? _getAllSettings; - private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod; - private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; - private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; - private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; - private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; - private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; + private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; + private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; + private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; + private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; + private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; + private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; + private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; + private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp? _getCurrentSettingsWithTemp; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; + private global::Penumbra.Api.IpcSubscribers.GetAllModSettings? _getAllSettings; + private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod; + private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; + private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; + private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; + private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; + private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; + private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; + private Func? _checkCurrentChangedItems; private readonly IDisposable _initializedEvent; private readonly IDisposable _disposedEvent; @@ -195,37 +199,58 @@ public class PenumbraService : IDisposable return ret[0]; } - public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() + public IReadOnlyList<(Mod Mod, ModSettings Settings, int Count)> GetMods(IReadOnlyList data) { if (!Available) return []; try { - var allMods = _getMods!.Invoke(); - var collection = _currentCollection!.Invoke(ApiCollectionType.Current); - if (_getAllSettings != null) + var allMods = _getMods!.Invoke(); + var currentCollection = _currentCollection!.Invoke(ApiCollectionType.Current); + var withSettings = WithSettings(allMods, currentCollection!.Value.Id); + var withCounts = WithCounts(withSettings, allMods.Count); + return OrderList(withCounts, allMods.Count); + + IEnumerable<(Mod Mod, ModSettings Settings)> WithSettings(Dictionary mods, Guid collection) { - var allSettings = _getAllSettings.Invoke(collection!.Value.Id, false, false, Key); - if (allSettings.Item1 is PenumbraApiEc.Success) - return allMods.Select(m => (new Mod(m.Value, m.Key), + if (_getAllSettings != null) + { + var allSettings = _getAllSettings.Invoke(collection, false, false, Key); + if (allSettings.Item1 is PenumbraApiEc.Success) + return mods.Select(m => (new Mod(m.Value, m.Key), allSettings.Item2!.TryGetValue(m.Key, out var s) - ? new ModSettings(s.Item3, s.Item2, s.Item1, s.Item4 && s.Item5, false) - : ModSettings.Empty)) - .OrderByDescending(p => p.Item2.Enabled) - .ThenBy(p => p.Item1.Name) - .ThenBy(p => p.Item1.DirectoryName) - .ThenByDescending(p => p.Item2.Priority) - .ToList(); + ? new ModSettings(s.Item3, s.Item2, s.Item1, s is { Item4: true, Item5: true }, false) + : ModSettings.Empty)); + } + + return mods.Select(m => (new Mod(m.Value, m.Key), GetSettings(collection, m.Key, m.Value, out _))); } - return allMods - .Select(m => (new Mod(m.Value, m.Key), GetSettings(collection!.Value.Id, m.Key, m.Value, out _))) - .OrderByDescending(p => p.Item2.Enabled) - .ThenBy(p => p.Item1.Name) - .ThenBy(p => p.Item1.DirectoryName) - .ThenByDescending(p => p.Item2.Priority) - .ToList(); + IEnumerable<(Mod Mod, ModSettings Settings, int Count)> WithCounts(IEnumerable<(Mod Mod, ModSettings Settings)> mods, int count) + { + if (_changedItems != null && _changedItems.Count == count) + return mods.Select((m, idx) => (m.Mod, m.Settings, CountItems(_changedItems[idx].ChangedItems, data))); + + return mods.Select(p => (p.Item1, p.Item2, CountItems(_getChangedItems!.Invoke(p.Item1.DirectoryName, p.Item1.Name), data))); + + static int CountItems(IReadOnlyDictionary dict, IReadOnlyList data) + => data.Count(dict.ContainsKey); + } + + static IReadOnlyList<(Mod Mod, ModSettings Settings, int Count)> OrderList( + IEnumerable<(Mod Mod, ModSettings Settings, int Count)> enumerable, int count) + { + var array = new (Mod Mod, ModSettings Settings, int Count)[count]; + var i = 0; + foreach (var t in enumerable.OrderByDescending(p => p.Item2.Enabled) + .ThenByDescending(p => p.Item3) + .ThenBy(p => p.Item1.Name) + .ThenBy(p => p.Item1.DirectoryName) + .ThenByDescending(p => p.Item2.Priority)) + array[i++] = t; + return array; + } } catch (Exception ex) { @@ -289,6 +314,9 @@ public class PenumbraService : IDisposable RemoveAllTemporarySettings(collection.Key); } + public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) + => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index) { var ex = settings.Remove @@ -476,6 +504,7 @@ public class PenumbraService : IDisposable _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); + _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp) { _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); @@ -488,10 +517,16 @@ public class PenumbraService : IDisposable if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp3) { _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp4) + { + _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); + _checkCurrentChangedItems = + new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); + } } } } @@ -541,6 +576,9 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; _queryTemporaryModSettings = null; + _getChangedItems = null; + _changedItems = null; + _checkCurrentChangedItems = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/OtterGui b/OtterGui index 3c1260c..0b6085c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3c1260c9833303c2d33d12d6f77dc2b1afea3f34 +Subproject commit 0b6085ce720ffb7c78cf42d4e51861f34db27744 diff --git a/Penumbra.Api b/Penumbra.Api index c678090..70f0468 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c67809057fac73a0fd407e3ad567f0aa6bc0bc37 +Subproject commit 70f046830cc7cd35b3480b12b7efe94182477fbb