From 02bfd177943f55c887ec12010b8eb3b36cdced1a Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 11 Jan 2025 12:49:40 +0000 Subject: [PATCH 001/212] [CI] Updating repo.json for 1.3.5.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 5420efa..a5754ab 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.5.0", - "TestingAssemblyVersion": "1.3.5.0", + "AssemblyVersion": "1.3.5.1", + "TestingAssemblyVersion": "1.3.5.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c83ddf054ae3e7dc3815aa577279d87e4809dcd7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 12 Jan 2025 00:03:54 +0100 Subject: [PATCH 002/212] Add counts to multi design selection. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 115 ++++++++++-------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 6e5b1b1..a1f17d4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,79 +3,95 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using ImGuiNET; using OtterGui; -using OtterGui.Filesystem; using OtterGui.Raii; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _editor, DesignColors _colors) +public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors) { - private readonly DesignColorCombo _colorCombo = new(_colors, true); + private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() { - if (_selector.SelectedPaths.Count == 0) + if (selector.SelectedPaths.Count == 0) return; var width = ImGuiHelpers.ScaledVector2(145, 0); ImGui.NewLine(); - DrawDesignList(); + var treeNodePos = ImGui.GetCursorPos(); + _numDesigns = DrawDesignList(); + DrawCounts(treeNodePos); var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); DrawMultiQuickDesignBar(offset); } - private void DrawDesignList() + private void DrawCounts(Vector2 treeNodePos) { - using var tree = ImRaii.TreeNode("Currently Selected Objects", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); + var startPos = ImGui.GetCursorPos(); + var numFolders = selector.SelectedPaths.Count - _numDesigns; + var text = (_numDesigns, numFolders) switch + { + (0, 0) => string.Empty, // should not happen + (> 0, 0) => $"{_numDesigns} Designs", + (0, > 0) => $"{numFolders} Folders", + _ => $"{_numDesigns} Designs, {numFolders} Folders", + }; + ImGui.SetCursorPos(treeNodePos); + ImUtf8.TextRightAligned(text); + ImGui.SetCursorPos(startPos); + } + + private int DrawDesignList() + { + using var tree = ImUtf8.TreeNode("Currently Selected Objects"u8, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGui.Separator(); if (!tree) - return; + return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); - var sizeType = ImGui.GetFrameHeight(); - var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; + var sizeType = new Vector2(ImGui.GetFrameHeight()); + var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType.X - 4 * ImGui.GetStyle().CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; _numQuickDesignEnabled = 0; - _numDesigns = 0; - using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) + var numDesigns = 0; + using (var table = ImUtf8.Table("mods"u8, 3, ImGuiTableFlags.RowBg)) { if (!table) - return; + return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); - ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); - ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); - ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); + ImUtf8.TableSetupColumn("type"u8, ImGuiTableColumnFlags.WidthFixed, sizeType.X); + ImUtf8.TableSetupColumn("mod"u8, ImGuiTableColumnFlags.WidthFixed, sizeMods); + ImUtf8.TableSetupColumn("path"u8, ImGuiTableColumnFlags.WidthFixed, sizeFolders); var i = 0; - foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) + foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p)) .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) { using var id = ImRaii.PushId(i++); + var (icon, text) = path is DesignFileSystem.Leaf l + ? (FontAwesomeIcon.FileCircleMinus, l.Value.Name.Text) + : (FontAwesomeIcon.FolderMinus, string.Empty); ImGui.TableNextColumn(); - var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); - if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) - _selector.RemovePathFromMultiSelection(path); + if (ImUtf8.IconButton(icon, "Remove from selection."u8, sizeType)) + selector.RemovePathFromMultiSelection(path); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(fullName); + ImUtf8.DrawFrameColumn(text); + ImUtf8.DrawFrameColumn(fullName); if (path is not DesignFileSystem.Leaf l2) continue; - ++_numDesigns; + ++numDesigns; if (l2.Value.QuickDesign) ++_numQuickDesignEnabled; } } ImGui.Separator(); + return numDesigns; } private string _tag = string.Empty; @@ -86,12 +102,11 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager private float DrawMultiTagger(Vector2 width) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi Tagger:"); + ImUtf8.TextFrameAligned("Multi Tagger:"u8); ImGui.SameLine(); var offset = ImGui.GetItemRectSize().X; ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); - ImGui.InputTextWithHint("##tag", "Tag Name...", ref _tag, 128); + ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); UpdateTagCache(); var label = _addDesigns.Count > 0 @@ -103,9 +118,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager : $"All designs selected already contain the tag \"{_tag}\"." : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) foreach (var design in _addDesigns) - _editor.AddTag(design, _tag); + editor.AddTag(design, _tag); label = _removeDesigns.Count > 0 ? $"Remove from {_removeDesigns.Count} Designs" @@ -116,41 +131,39 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager : $"No selected design contains the tag \"{_tag}\" locally." : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) foreach (var (design, index) in _removeDesigns) - _editor.RemoveTag(design, index); + editor.RemoveTag(design, index); ImGui.Separator(); return offset; } private void DrawMultiQuickDesignBar(float offset) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi QDB:"); + ImUtf8.TextFrameAligned("Multi QDB:"u8); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numQuickDesignEnabled; var tt = diff == 0 ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; - if (ImGuiUtil.DrawDisabledButton("Display Selected Designs in QDB", buttonWidth, tt, diff == 0)) - foreach(var design in _selector.SelectedPaths.OfType()) - _editor.SetQuickDesign(design.Value, true); + if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetQuickDesign(design.Value, true); ImGui.SameLine(); tt = _numQuickDesignEnabled == 0 ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; - if (ImGuiUtil.DrawDisabledButton("Hide Selected Designs in QDB", buttonWidth, tt, _numQuickDesignEnabled == 0)) - foreach (var design in _selector.SelectedPaths.OfType()) - _editor.SetQuickDesign(design.Value, false); + if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetQuickDesign(design.Value, false); ImGui.Separator(); } private void DrawMultiColor(Vector2 width, float offset) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi Colors:"); + ImUtf8.TextFrameAligned("Multi Colors:"); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); @@ -168,9 +181,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager } : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) foreach (var design in _addDesigns) - _editor.ChangeColor(design, _colorCombo.CurrentSelection!); + editor.ChangeColor(design, _colorCombo.CurrentSelection!); label = _removeDesigns.Count > 0 ? $"Unset {_removeDesigns.Count} Designs" @@ -179,9 +192,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager ? "No selected design is set to a non-automatic color." : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) foreach (var (design, _) in _removeDesigns) - _editor.ChangeColor(design, string.Empty); + editor.ChangeColor(design, string.Empty); ImGui.Separator(); } @@ -193,7 +206,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager if (_tag.Length == 0) return; - foreach (var leaf in _selector.SelectedPaths.OfType()) + foreach (var leaf in selector.SelectedPaths.OfType()) { var index = leaf.Value.Tags.IndexOf(_tag); if (index >= 0) @@ -208,7 +221,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _addDesigns.Clear(); _removeDesigns.Clear(); var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; - foreach (var leaf in _selector.SelectedPaths.OfType()) + foreach (var leaf in selector.SelectedPaths.OfType()) { if (leaf.Value.Color.Length > 0) _removeDesigns.Add((leaf.Value, 0)); From 60a1ee728ad67750f219fccb5c58fc73c40aa8d5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 14 Jan 2025 14:51:36 +0100 Subject: [PATCH 003/212] Use current temporary settings for mod associations if they exist. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 6 ++++- Glamourer/Interop/Penumbra/PenumbraService.cs | 26 +++++++++++++++---- Penumbra.Api | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 601d3ae..c8a4b11 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -118,7 +118,7 @@ public class EquipmentDrawer public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (mainhand.CurrentItem.PrimaryId.Id == 0) + if (mainhand.CurrentItem.PrimaryId.Id == 0 && !allWeapons) return; if (_config.HideApplyCheckmarks) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index b02ece6..5eda7cc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -149,12 +149,14 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImUtf8.IconButton(FontAwesomeIcon.RedoAlt, "Update the settings of this mod association."u8); if (ImGui.IsItemHovered()) { - var newSettings = penumbra.GetModSettings(mod); + var newSettings = penumbra.GetModSettings(mod, out var source); if (ImGui.IsItemClicked()) updatedMod = (mod, newSettings); using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); using var tt = ImUtf8.Tooltip(); + if (source.Length > 0) + ImUtf8.Text($"Using temporary settings made by {source}."); ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); @@ -162,6 +164,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect { if (namesDifferent) ImUtf8.Text("Directory Name"u8); + ImUtf8.Text("Force Inherit"u8); ImUtf8.Text("Enabled"u8); ImUtf8.Text("Priority"u8); ModCombo.DrawSettingsLeft(newSettings); @@ -173,6 +176,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (namesDifferent) ImUtf8.Text(mod.DirectoryName); + ImUtf8.Text(newSettings.ForceInherit.ToString()); ImUtf8.Text(newSettings.Enabled.ToString()); ImUtf8.Text(newSettings.Priority.ToString()); ModCombo.DrawSettingsRight(newSettings); diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 868ce11..13be628 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -33,9 +33,10 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 3; - public const int RequiredPenumbraFeatureVersionTemp = 4; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 3; + public const int RequiredPenumbraFeatureVersionTemp = 4; + public const int RequiredPenumbraFeatureVersionTemp2 = 5; private const int Key = -1610; @@ -67,6 +68,7 @@ public class PenumbraService : IDisposable 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 readonly IDisposable _initializedEvent; @@ -128,19 +130,30 @@ public class PenumbraService : IDisposable public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; - public ModSettings GetModSettings(in Mod mod) + public ModSettings GetModSettings(in Mod mod, out string source) { + source = string.Empty; if (!Available) return ModSettings.Empty; try { var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + if (_queryTemporaryModSettings != null) + { + var tempEc = _queryTemporaryModSettings.Invoke(collection!.Value.Id, mod.DirectoryName, out var tempTuple, out source); + if (tempEc is PenumbraApiEc.Success && tempTuple != null) + return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, + tempTuple.Value.ForceInherit, false); + } + var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; - return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty; + return tuple.HasValue + ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) + : ModSettings.Empty; } catch (Exception ex) { @@ -441,6 +454,8 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); } Available = true; @@ -485,6 +500,7 @@ public class PenumbraService : IDisposable _removeTemporaryModSettingsPlayer = null; _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; + _queryTemporaryModSettings = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/Penumbra.Api b/Penumbra.Api index f60de67..b4e716f 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f60de67d24afe6e175f17d03cd234f493ea91265 +Subproject commit b4e716f86d94cd4d98d8f58e580ed5f619ea87ae From 8160f420db3ac048021bb1606775b5b1c35cf96d Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 15 Jan 2025 17:05:57 +0000 Subject: [PATCH 004/212] [CI] Updating repo.json for testing_1.3.5.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a5754ab..dab02ea 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.1", + "TestingAssemblyVersion": "1.3.5.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c605d1951031e95f0bc5923ebaa986f8f1057472 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Thu, 16 Jan 2025 19:34:46 -0800 Subject: [PATCH 005/212] Progress made. May be successful! --- Glamourer/Api/StateApi.cs | 2 + Glamourer/Automation/AutoDesignApplier.cs | 12 ++ Glamourer/Interop/ChangeCustomizeService.cs | 7 +- Glamourer/Interop/InventoryService.cs | 82 ++++++++----- Glamourer/Interop/JobService.cs | 2 +- Glamourer/Interop/UpdateSlotService.cs | 124 ++++++++++++++++++-- Glamourer/State/StateEditor.cs | 26 ++-- Glamourer/State/StateListener.cs | 5 +- Glamourer/State/StateManager.cs | 4 +- 9 files changed, 201 insertions(+), 63 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 331942b..ce7d226 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -333,6 +333,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { + Glamourer.Log.Error($"[OnStateChanged API CALL] Sending out OnStateChanged with type {type}."); + if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 660acf4..d49864f 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Designs.Links; @@ -201,11 +202,22 @@ public sealed class AutoDesignApplier : IDisposable } } + /// + /// JOB CHANGE IS CALLED UPON HERE. + /// private void OnJobChange(Actor actor, Job oldJob, Job newJob) { + unsafe + { + var drawObject = actor.AsCharacter->DrawObject; + Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] 0x{(nint)drawObject:X} changed job from {oldJob} ({oldJob.Id}) to {newJob} ({newJob.Id})."); + } + if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; + Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] We had EnableAutoDesigns active, and are a valid actor!"); + if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 495d69c..032412e 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -64,12 +64,13 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _changeCustomizeHook; + // manual invoke by calling the detours _original call to `execute to` instead of `listen to`. public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) return false; - Glamourer.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}."); + Glamourer.Log.Information($"[ChangeCustomize] Glamour-Invoked on 0x{model.Address:X} with {customize}."); using var _ = InUpdate.EnterMethod(); var ret = _original(model.AsHuman, customize.Data, true); return ret; @@ -78,16 +79,20 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 UpdateCustomize(actor.Model, customize); + // detoured method. private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) Invoke(human, ref *(CustomizeArray*)data); var ret = _changeCustomizeHook.Original(human, data, skipEquipment); + + Glamourer.Log.Information($"[ChangeCustomize] Called on with {*(CustomizeArray*)data} ({ret})."); _postEvent.Invoke(human); return ret; } + public void Subscribe(Action action, Post.Priority priority) => _postEvent.Subscribe(action, priority); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6d8e58b..605ca15 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -1,9 +1,11 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -16,39 +18,48 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); + // This can be moved into client structs or penumbra.gamedata when needed. + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); + + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = - interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + interop.InitializeFromAttributes(this); _moveItemHook.Enable(); _equipGearsetHook.Enable(); + _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); _equipGearsetHook.Dispose(); + _equipGearsetInternalHook.Dispose(); } private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); private readonly Hook _equipGearsetHook; - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset(gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + Glamourer.Log.Warning($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { - var entry = module->GetGearset(gearsetId); + var entry = module->GetGearset((int)gearsetId); if (entry == null) return ret; @@ -73,18 +84,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); - Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); - Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); - Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); - Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); - Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); - Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); - Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); - Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); - Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); - Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); - Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { @@ -99,17 +110,17 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } Add(EquipSlot.MainHand, ref entry->Items[0]); - Add(EquipSlot.OffHand, ref entry->Items[1]); - Add(EquipSlot.Head, ref entry->Items[2]); - Add(EquipSlot.Body, ref entry->Items[3]); - Add(EquipSlot.Hands, ref entry->Items[5]); - Add(EquipSlot.Legs, ref entry->Items[6]); - Add(EquipSlot.Feet, ref entry->Items[7]); - Add(EquipSlot.Ears, ref entry->Items[8]); - Add(EquipSlot.Neck, ref entry->Items[9]); - Add(EquipSlot.Wrists, ref entry->Items[10]); - Add(EquipSlot.RFinger, ref entry->Items[11]); - Add(EquipSlot.LFinger, ref entry->Items[12]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -118,6 +129,13 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } + private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + { + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + return ret; + } + private static uint FixId(uint itemId) => itemId % 50000; @@ -130,7 +148,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService InventoryType targetContainer, ushort targetSlot, byte unk) { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); - Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); if (ret == 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 1797809..f687715 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -50,7 +50,7 @@ public class JobService : IDisposable var newJob = Jobs.TryGetValue(newJobIndex, out var j) ? j : Jobs[0]; var oldJob = Jobs.TryGetValue(oldJobIndex, out var o) ? o : Jobs[0]; - Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {newJob}."); + Glamourer.Log.Error($"{actor} changed job from {oldJob} to {newJob}."); JobChanged?.Invoke(actor, oldJob, newJob); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index d1004e6..e43dc74 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -1,36 +1,124 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; - namespace Glamourer.Interop; +/// +/// This struct is the struct that loadallequipment passes in as its gearsetData container. +/// +[StructLayout(LayoutKind.Explicit)] // Size of 70 bytes maybe? +public readonly struct GearsetItemDataStruct +{ + // Stores the weapon data. Includes both dyes in the data. + [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; + [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; + + [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 + [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. + + // Flicks from 0 to 128 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + [FieldOffset(18)] public readonly byte UNK_18; + [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. + + // Legacy helmet equip slot armor for a character. + [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; + [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; + [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; + [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; + [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; + [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; + [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; + [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; + [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; + [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; + + // Byte array of all slot's secondary dyes. + [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; + [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; + [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; + [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; + [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; + [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; + [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; + [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; + [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; + [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; +} + public unsafe class UpdateSlotService : IDisposable { - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; - public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - private readonly DictBonusItems _bonusItems; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + private readonly DictBonusItems _bonusItems; + + #region LoadAllEquipData + /////////////////////////////////////////////////// + // This is a currently undocumented signature that loads all equipment after changing a gearset. + // :: Signature Maintainers Note: + // To obtain this signature, get the stacktrace from FlagSlotForUpdate for human, and find func `sub_140842F50`. + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. + // + // By detouring this function, and executing the original, then logic after, we have a consistant point in time where we know all + // slots have been flagged, meaning a consistant point in time that glamourer has processed all of its updates. + public const string LoadAllEquipmentSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadAllEquipmentDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + private Int64 LoadAllEquipmentDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + { + // return original first so we can log the changes after + var ret = _loadAllEquipmentHook.Original(drawDataContainer, gearsetData); + + // perform logic stuff. + var owner = drawDataContainer->OwnerObject; + Glamourer.Log.Warning($"[LoadAllEquipmentDetour] Owner: 0x{(nint)owner->DrawObject:X} Finished Applying its GameState!"); + Glamourer.Log.Warning($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + + // return original. + return ret; + } + + private string FormatWeaponModelId(WeaponModelId weaponModelId) => $"Id: {weaponModelId.Id}, Type: {weaponModelId.Type}, Variant: {weaponModelId.Variant}, Stain0: {weaponModelId.Stain0}, Stain1: {weaponModelId.Stain1}"; + + private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetItemData) + { + string ret = $"\nMainhandWeaponData: {FormatWeaponModelId(gearsetItemData.MainhandWeaponData)}," + + $"\nOffhandWeaponData: {FormatWeaponModelId(gearsetItemData.OffhandWeaponData)}," + + $"\nCrestBitField: {gearsetItemData.CrestBitField} | JobId: {gearsetItemData.JobId} | UNK_18: {gearsetItemData.UNK_18} | UNK_19: {gearsetItemData.UNK_19}"; + // Iterate through offsets from 20 to 60 and format the CharacterArmor data + for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + { + LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetItemData + offset); + int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + byte* dyePtr = (byte*)&gearsetItemData + dyeOffset; + ret += $"\nEquipSlot {((EquipSlot)(dyeOffset-60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + } + return ret; + } +#endregion LoadAllEquipData public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; - _bonusItems = bonusItems; + _bonusItems = bonusItems; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); + _loadAllEquipmentHook.Enable(); } public void Dispose() { _flagSlotForUpdateHook.Dispose(); _flagBonusSlotForUpdateHook.Dispose(); + _loadAllEquipmentHook.Dispose(); } public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data) @@ -79,24 +167,36 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; + [Signature(LoadAllEquipmentSig, DetourName = nameof(LoadAllEquipmentDetour))] + private readonly Hook _loadAllEquipmentHook = null!; + private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; + EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + Glamourer.Log.Information($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + + return returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; + BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + Glamourer.Log.Information($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + + return returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) - => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); + { + Glamourer.Log.Warning($"Glamour-Invoked Equip Slot update for 0x{drawObject.Address:X} with {slot} and {armor}."); + return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); + } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index f9ddb89..b592b65 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -51,7 +51,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Information( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } @@ -64,7 +64,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Information( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, new EntireCustomizeTransaction(applied, old, customizeInput)); @@ -75,7 +75,10 @@ public class StateEditor( { var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) + { + Glamourer.Log.Information("Not Setting State or invoking, Editor requested us not to change it!"); return; + } var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; var actors = type is StateChangeType.Equip @@ -86,8 +89,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, null, settings); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeItem] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { @@ -116,8 +119,8 @@ public class StateEditor( return; var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeBonus] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } @@ -149,8 +152,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, stains, settings); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeEquip] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); @@ -181,7 +184,7 @@ public class StateEditor( return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } @@ -250,7 +253,7 @@ public class StateEditor( return; var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } @@ -414,8 +417,7 @@ public class StateEditor( ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - Glamourer.Log.Verbose( - $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d054a25..382e488 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -216,8 +216,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) - && _manager.TryGetValue(identifier, out var state)) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); locked = state.Sources[slot, false] is StateSource.IpcFixed; @@ -383,7 +382,7 @@ public class StateListener : IDisposable lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index eabaf2f..736dd6e 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -273,7 +273,7 @@ public sealed class StateManager( if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } @@ -298,7 +298,7 @@ public sealed class StateManager( state.Materials.Clear(); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } From e1a41b5f3c7d144cae20c291ff2ee2fe77978e87 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 17:39:26 -0800 Subject: [PATCH 006/212] Implements true endpoints for all glamourer operations, also correctly marks reverts and gearsets. Replaced back excessive logging to maintain with logging formats expected by glamourer. --- Glamourer/Api/DesignsApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + Glamourer/Api/StateApi.cs | 27 ++++- Glamourer/Automation/AutoDesignApplier.cs | 2 - Glamourer/Designs/IDesignEditor.cs | 6 +- Glamourer/Events/GearsetDataLoaded.cs | 23 ++++ Glamourer/Events/StateUpdated.cs | 26 +++++ Glamourer/Gui/DesignQuickBar.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 14 +-- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 4 +- Glamourer/Interop/InventoryService.cs | 10 +- Glamourer/Interop/JobService.cs | 2 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 4 +- Glamourer/Interop/UpdateSlotService.cs | 100 ++++++++---------- Glamourer/Services/CommandService.cs | 8 +- Glamourer/State/StateEditor.cs | 5 + Glamourer/State/StateListener.cs | 29 ++++- Glamourer/State/StateManager.cs | 43 +++++++- 21 files changed, 225 insertions(+), 99 deletions(-) create mode 100644 Glamourer/Events/GearsetDataLoaded.cs create mode 100644 Glamourer/Events/StateUpdated.cs diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index ee49bd5..6c3037e 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -33,7 +33,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0); + ResetMaterials: !once && key != 0, SendStateUpdate: true); using var restrict = ApiHelpers.Restrict(design, flags); stateManager.ApplyDesign(state, design, settings); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 8639a22..166245f 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -52,6 +52,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), + IpcSubscribers.StateUpdated.Provider(pi, api.State), IpcSubscribers.GPoseChanged.Provider(pi, api.State), ]; _initializedProvider.Invoke(); diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index ce7d226..248e294 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -11,9 +11,10 @@ using OtterGui.Services; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; +using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; - + public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -23,6 +24,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly AutoDesignApplier _autoDesigns; private readonly ObjectManager _objects; private readonly StateChanged _stateChanged; + private readonly StateUpdated _stateUpdated; private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, @@ -32,6 +34,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable AutoDesignApplier autoDesigns, ObjectManager objects, StateChanged stateChanged, + StateUpdated stateUpdated, GPoseService gPose) { _helpers = helpers; @@ -41,8 +44,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _autoDesigns = autoDesigns; _objects = objects; _stateChanged = stateChanged; + _stateUpdated = stateUpdated; _gPose = gPose; _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); + _stateUpdated.Subscribe(OnStateUpdated, Events.StateUpdated.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); } @@ -250,13 +255,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public event Action? StateChanged; public event Action? StateChangedWithType; + public event Action? StateUpdated; public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0); + ResetMaterials: !once && key != 0, SendStateUpdate: true); _stateManager.ApplyDesign(state, design, settings); ApiHelpers.Lock(state, key, flags); } @@ -296,7 +302,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, out var forcedRedraw); - _stateManager.ReapplyState(actor, state, forcedRedraw, source); + _stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source); ApiHelpers.Lock(state, key, flags); } @@ -333,7 +339,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - Glamourer.Log.Error($"[OnStateChanged API CALL] Sending out OnStateChanged with type {type}."); + // Remove this comment before creating PR. + Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); if (StateChanged != null) foreach (var actor in actors.Objects) @@ -343,4 +350,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable foreach (var actor in actors.Objects) StateChangedWithType.Invoke(actor.Address, type); } + + private void OnStateUpdated(StateUpdateType type, ActorData actors) + { + if (StateUpdated != null) + foreach (var actor in actors.Objects) + { + // Remove these before creating PR + Glamourer.Log.Information($"[ENDPOINT DEBUGGING] 0x{actor.Address:X} had update of type {type}."); + Glamourer.Log.Information("--------------------------------------------------------------"); + StateUpdated.Invoke(actor.Address, type); + } + } } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index d49864f..bcc907c 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -216,8 +216,6 @@ public sealed class AutoDesignApplier : IDisposable if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; - Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] We had EnableAutoDesigns active, and are a valid actor!"); - if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 935263b..0178620 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -13,7 +13,8 @@ public readonly record struct ApplySettings( bool FromJobChange = false, bool UseSingleSource = false, bool MergeLinks = false, - bool ResetMaterials = false) + bool ResetMaterials = false, + bool SendStateUpdate = false) { public static readonly ApplySettings Manual = new() { @@ -24,6 +25,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = false, + SendStateUpdate = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -35,6 +37,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = true, ResetMaterials = false, + SendStateUpdate = false, }; public static readonly ApplySettings Game = new() @@ -46,6 +49,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = true, + SendStateUpdate = false, }; } diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs new file mode 100644 index 0000000..47d0108 --- /dev/null +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -0,0 +1,23 @@ +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; + +namespace Glamourer.Events; + +/// +/// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. +/// This defines a universal endpoint of base game state application to monitor. +/// +/// The model drawobject associated with the finished load (should always be ClientPlayer) +/// +/// +public sealed class GearsetDataLoaded() + : EventWrapper(nameof(GearsetDataLoaded)) +{ + public enum Priority + { + /// + StateListener = 0, + } +} \ No newline at end of file diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs new file mode 100644 index 0000000..82d737f --- /dev/null +++ b/Glamourer/Events/StateUpdated.cs @@ -0,0 +1,26 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs.History; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when a Design is edited in any way. +/// +/// Parameter is the type of the change +/// Parameter is the changed saved state. +/// Parameter is the existing actors using this saved state. +/// Parameter is any additional data depending on the type of change. +/// +/// +public sealed class StateUpdated() + : EventWrapper(nameof(StateUpdated)) +{ + public enum Priority + { + /// + GlamourerIpc = int.MinValue, + } +} diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 31ca45e..bdc345f 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -183,7 +183,7 @@ public sealed class DesignQuickBar : Window, IDisposable } using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } private void DrawRevertButton(Vector2 buttonSize) @@ -213,7 +213,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetState(state!, StateSource.Manual); + _stateManager.ResetState(state!, StateSource.Manual, stateUpdate: true); } private void DrawRevertAutomationButton(Vector2 buttonSize) @@ -252,7 +252,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual); } } @@ -292,7 +292,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 265e1d9..ced78fb 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -385,7 +385,7 @@ public class ActorPanel { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual); + _stateManager.ResetState(_state!, StateSource.Manual, stateUpdate: true); ImGui.SameLine(); @@ -394,7 +394,7 @@ public class ActorPanel !_config.EnableAutoDesigns || _state!.IsLocked)) { _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw); - _stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual); } ImGui.SameLine(); @@ -403,14 +403,14 @@ public class ActorPanel !_config.EnableAutoDesigns || _state!.IsLocked)) { _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw); - _stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual); } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Reapply", Vector2.Zero, "Try to reapply the configured state if something went wrong. Should generally not be necessary.", _state!.IsLocked)) - _stateManager.ReapplyState(_actor, false, StateSource.Manual); + _stateManager.ReapplyState(_actor, false, StateSource.Manual, true); } private void DrawApplyToSelf() @@ -423,7 +423,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual); + ApplySettings.Manual with { SendStateUpdate = true }); } private void DrawApplyToTarget() @@ -440,7 +440,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual); + ApplySettings.Manual with { SendStateUpdate = true }); } @@ -467,7 +467,7 @@ public class ActorPanel var text = ImGui.GetClipboardText(); var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 394bd7f..c44a722 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -79,7 +79,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); - _state.ApplyDesign(state!, design, ApplySettings.Manual); + _state.ApplyDesign(state!, design, ApplySettings.Manual with { SendStateUpdate = true }); } using (ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 070ca1e..fe71609 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -460,7 +460,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } @@ -478,7 +478,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 312bceb..345df11 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -196,7 +196,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); } } @@ -214,7 +214,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); } } diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 032412e..10e3a12 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -70,7 +70,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _itemList = new(12); - // This can be moved into client structs or penumbra.gamedata when needed. + // Called by EquipGearset, but returns a pointer instead of an int. + // This is the internal function processed by all sources of Equipping a gearset, + // such as hotbar gearset application and command gearset application public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); @@ -56,7 +58,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Warning($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { var entry = module->GetGearset((int)gearsetId); @@ -132,7 +134,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); return ret; } @@ -148,7 +150,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService InventoryType targetContainer, ushort targetSlot, byte unk) { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); - Glamourer.Log.Verbose($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); + Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); if (ret == 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index f687715..1797809 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -50,7 +50,7 @@ public class JobService : IDisposable var newJob = Jobs.TryGetValue(newJobIndex, out var j) ? j : Jobs[0]; var oldJob = Jobs.TryGetValue(oldJobIndex, out var o) ? o : Jobs[0]; - Glamourer.Log.Error($"{actor} changed job from {oldJob} to {newJob}."); + Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {newJob}."); JobChanged?.Invoke(actor, oldJob, newJob); } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index fbe0d9d..3e48fe9 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -88,7 +88,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _actions.Enqueue((state, () => { foreach (var actor in actors.Objects) - _state.ReapplyState(actor, state, false, StateSource.IpcManual); + _state.ReapplyState(actor, state, false, StateSource.IpcManual, true); Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); }, WaitFrames)); } @@ -108,7 +108,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _frame = currentFrame; _framework.RunOnFrameworkThread(() => { - _state.ReapplyState(_objects.Player, false, StateSource.IpcManual); + _state.ReapplyState(_objects.Player, false, StateSource.IpcManual, true); Glamourer.Log.Debug( $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); }); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index e43dc74..7e5cf59 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -23,7 +23,7 @@ public readonly struct GearsetItemDataStruct [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - // Flicks from 0 to 128 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. [FieldOffset(18)] public readonly byte UNK_18; [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. @@ -56,69 +56,47 @@ public unsafe class UpdateSlotService : IDisposable { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; - #region LoadAllEquipData - /////////////////////////////////////////////////// - // This is a currently undocumented signature that loads all equipment after changing a gearset. - // :: Signature Maintainers Note: - // To obtain this signature, get the stacktrace from FlagSlotForUpdate for human, and find func `sub_140842F50`. - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. - // - // By detouring this function, and executing the original, then logic after, we have a consistant point in time where we know all - // slots have been flagged, meaning a consistant point in time that glamourer has processed all of its updates. - public const string LoadAllEquipmentSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadAllEquipmentDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - private Int64 LoadAllEquipmentDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) { - // return original first so we can log the changes after - var ret = _loadAllEquipmentHook.Original(drawDataContainer, gearsetData); + // Let the gearset data process all of its loads and slot flag update calls first. + var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); + // Ensure that the owner of the drawdata container is a character base. + Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; + if (!ownerDrawObject.IsCharacterBase) + return ret; - // perform logic stuff. - var owner = drawDataContainer->OwnerObject; - Glamourer.Log.Warning($"[LoadAllEquipmentDetour] Owner: 0x{(nint)owner->DrawObject:X} Finished Applying its GameState!"); - Glamourer.Log.Warning($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - - // return original. + // invoke the changed event for the state listener and return. + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); return ret; } - private string FormatWeaponModelId(WeaponModelId weaponModelId) => $"Id: {weaponModelId.Id}, Type: {weaponModelId.Type}, Variant: {weaponModelId.Variant}, Stain0: {weaponModelId.Stain0}, Stain1: {weaponModelId.Stain1}"; - - private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetItemData) - { - string ret = $"\nMainhandWeaponData: {FormatWeaponModelId(gearsetItemData.MainhandWeaponData)}," + - $"\nOffhandWeaponData: {FormatWeaponModelId(gearsetItemData.OffhandWeaponData)}," + - $"\nCrestBitField: {gearsetItemData.CrestBitField} | JobId: {gearsetItemData.JobId} | UNK_18: {gearsetItemData.UNK_18} | UNK_19: {gearsetItemData.UNK_19}"; - // Iterate through offsets from 20 to 60 and format the CharacterArmor data - for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) - { - LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetItemData + offset); - int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset - byte* dyePtr = (byte*)&gearsetItemData + dyeOffset; - ret += $"\nEquipSlot {((EquipSlot)(dyeOffset-60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; - } - return ret; - } -#endregion LoadAllEquipData - - public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, - DictBonusItems bonusItems) + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, + IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; + GearsetDataLoadedEvent = gearsetDataLoaded; + _bonusItems = bonusItems; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); - _loadAllEquipmentHook.Enable(); + _loadGearsetDataHook.Enable(); } public void Dispose() { _flagSlotForUpdateHook.Dispose(); _flagBonusSlotForUpdateHook.Dispose(); - _loadAllEquipmentHook.Dispose(); + _loadGearsetDataHook.Dispose(); } public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data) @@ -167,18 +145,16 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; - [Signature(LoadAllEquipmentSig, DetourName = nameof(LoadAllEquipmentDetour))] - private readonly Hook _loadAllEquipmentHook = null!; + [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] + private readonly Hook _loadGearsetDataHook = null!; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; - EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Information($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; } @@ -186,17 +162,35 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; - BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Information($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) { - Glamourer.Log.Warning($"Glamour-Invoked Equip Slot update for 0x{drawObject.Address:X} with {slot} and {armor}."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } + + // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. + private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) + { + string ret = + $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; + // Iterate through offsets from 20 to 60 and format the CharacterArmor data + for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + { + LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); + int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + byte* dyePtr = (byte*)&gearsetData + dyeOffset; + ret += $"\nEquipSlot {((EquipSlot)(dyeOffset - 60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + } + return ret; + } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 10f68ee..a869a09 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -329,7 +329,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(identifier, actor, out var state)) { _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, revert, StateSource.Manual); } } } @@ -378,7 +378,7 @@ public class CommandService : IDisposable, IApiService return true; foreach (var actor in data.Objects) - _stateManager.ReapplyState(actor, false, StateSource.Manual); + _stateManager.ReapplyState(actor, false, StateSource.Manual, true); } @@ -668,7 +668,7 @@ public class CommandService : IDisposable, IApiService if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } else { @@ -677,7 +677,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b592b65..e1bd6a4 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -17,6 +17,7 @@ public class StateEditor( InternalStateEditor editor, StateApplier applier, StateChanged stateChanged, + StateUpdated stateUpdated, JobChangeState jobChange, Configuration config, ItemManager items, @@ -27,6 +28,7 @@ public class StateEditor( protected readonly InternalStateEditor Editor = editor; protected readonly StateApplier Applier = applier; protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateUpdated StateUpdated = stateUpdated; protected readonly Configuration Config = config; protected readonly ItemManager Items = items; @@ -41,6 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); + StateUpdated.Invoke(StateUpdateType.ModelChange, actors); } /// @@ -419,6 +422,8 @@ public class StateEditor( Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later + if(settings.SendStateUpdate) + StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 382e488..4d10c49 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -14,6 +14,7 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; +using Glamourer.Api.Enums; namespace Glamourer.State; @@ -34,10 +35,12 @@ public class StateListener : IDisposable private readonly PenumbraService _penumbra; private readonly EquipSlotUpdating _equipSlotUpdating; private readonly BonusSlotUpdating _bonusSlotUpdating; + private readonly GearsetDataLoaded _gearsetDataLoaded; private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; + private readonly StateUpdated _stateUpdated; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -54,11 +57,11 @@ public class StateListener : IDisposable private ActorState? _customizeState; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, - EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, + EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateUpdated stateUpdated) { _manager = manager; _items = items; @@ -66,6 +69,7 @@ public class StateListener : IDisposable _actors = actors; _config = config; _equipSlotUpdating = equipSlotUpdating; + _gearsetDataLoaded = gearsetDataLoaded; _weaponLoading = weaponLoading; _visorState = visorState; _weaponVisibility = weaponVisibility; @@ -82,6 +86,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; + _stateUpdated = stateUpdated; Subscribe(); } @@ -259,6 +264,22 @@ public class StateListener : IDisposable } } + private void OnGearsetDataLoaded(Model model) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + // ensure actor and state are valid. + if (!actor.Identifier(_actors, out var identifier)) + return; + + _objects.Update(); + if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) + _stateUpdated.Invoke(StateUpdateType.Gearset, actors); + } + + private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { _objects.Update(); @@ -382,7 +403,7 @@ public class StateListener : IDisposable lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Verbose($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); @@ -765,6 +786,7 @@ public class StateListener : IDisposable _penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener); _bonusSlotUpdating.Subscribe(OnBonusSlotUpdating, BonusSlotUpdating.Priority.StateListener); + _gearsetDataLoaded.Subscribe(OnGearsetDataLoaded, GearsetDataLoaded.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); @@ -782,6 +804,7 @@ public class StateListener : IDisposable _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating); _bonusSlotUpdating.Unsubscribe(OnBonusSlotUpdating); + _gearsetDataLoaded.Unsubscribe(OnGearsetDataLoaded); _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 736dd6e..129f8bb 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -21,6 +21,7 @@ public sealed class StateManager( ActorManager _actors, ItemManager items, StateChanged @event, + StateUpdated @event2, StateApplier applier, InternalStateEditor editor, HumanModelList _humans, @@ -30,7 +31,7 @@ public sealed class StateManager( DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier, gPose), + : StateEditor(editor, applier, @event, @event2, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -235,7 +236,7 @@ public sealed class StateManager( public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - public void ResetState(ActorState state, StateSource source, uint key = 0) + public void ResetState(ActorState state, StateSource source, uint key = 0, bool stateUpdate = false) { if (!state.Unlock(key)) return; @@ -276,6 +277,9 @@ public sealed class StateManager( Glamourer.Log.Debug( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + // only invoke if we define this reset call as the final call in our state update. + if(stateUpdate) + StateUpdated.Invoke(StateUpdateType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -301,6 +305,8 @@ public sealed class StateManager( Glamourer.Log.Debug( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -318,6 +324,8 @@ public sealed class StateManager( actors = Applier.ChangeCustomize(state, true); Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -367,6 +375,8 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -443,21 +453,44 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isUpdate = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source); + ReapplyState(actor, state, forceRedraw, source, isUpdate); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isUpdate) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + if(isUpdate) + StateUpdated.Invoke(StateUpdateType.Reapply, data); + } + + /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. + public void ReapplyAutomationState(Actor actor, bool forceRedraw, bool wasReset, StateSource source) + { + if (!GetOrCreate(actor, out var state)) + return; + + ReapplyAutomationState(actor, state, forceRedraw, wasReset, source); + } + + /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. + public void ReapplyAutomationState(Actor actor, ActorState state, bool forceRedraw, bool wasReset, StateSource source) + { + var data = Applier.ApplyAll(state, + forceRedraw + || !actor.Model.IsHuman + || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + // invoke the automation update based on what reset is. + StateUpdated.Invoke(wasReset ? StateUpdateType.RevertAutomation : StateUpdateType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From 8b609e5f0506e4d64f843631e47ecc6adcd10e90 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 18:09:10 -0800 Subject: [PATCH 007/212] corrected comments and formatting to reflect Glamourer's main branch. --- Glamourer/Automation/AutoDesignApplier.cs | 9 ---- Glamourer/Events/GearsetDataLoaded.cs | 4 +- Glamourer/Events/StateUpdated.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 3 -- Glamourer/Interop/InventoryService.cs | 38 +++++++-------- Glamourer/Interop/UpdateSlotService.cs | 51 ++++++++++----------- Glamourer/State/StateEditor.cs | 25 +++++----- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 12 ++--- 9 files changed, 64 insertions(+), 84 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index bcc907c..52956cc 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -202,17 +202,8 @@ public sealed class AutoDesignApplier : IDisposable } } - /// - /// JOB CHANGE IS CALLED UPON HERE. - /// private void OnJobChange(Actor actor, Job oldJob, Job newJob) { - unsafe - { - var drawObject = actor.AsCharacter->DrawObject; - Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] 0x{(nint)drawObject:X} changed job from {oldJob} ({oldJob.Id}) to {newJob} ({newJob.Id})."); - } - if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 47d0108..680ae3f 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -1,7 +1,5 @@ using OtterGui.Classes; -using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; namespace Glamourer.Events; @@ -9,7 +7,7 @@ namespace Glamourer.Events; /// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. /// This defines a universal endpoint of base game state application to monitor. /// -/// The model drawobject associated with the finished load (should always be ClientPlayer) +/// The model drawobject associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs index 82d737f..f18a69a 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateUpdated.cs @@ -9,10 +9,8 @@ namespace Glamourer.Events; /// /// Triggered when a Design is edited in any way. /// -/// Parameter is the type of the change -/// Parameter is the changed saved state. +/// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. /// /// public sealed class StateUpdated() diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 10e3a12..495d69c 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -64,7 +64,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _changeCustomizeHook; - // manual invoke by calling the detours _original call to `execute to` instead of `listen to`. public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) @@ -79,7 +78,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 UpdateCustomize(actor.Model, customize); - // detoured method. private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) @@ -90,7 +88,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) => _postEvent.Subscribe(action, priority); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 743bea1..33ba5cf 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -5,7 +5,6 @@ using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using OtterGui.Services; -using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -14,10 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; - private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); - // Called by EquipGearset, but returns a pointer instead of an int. // This is the internal function processed by all sources of Equipping a gearset, // such as hotbar gearset application and command gearset application @@ -27,10 +22,16 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] private readonly Hook _equipGearsetInternalHook = null!; + // The following above is currently pending for an accepted PR in FFXIVCLientStructs. + // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. + + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; + private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); @@ -58,7 +59,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Verbose($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { var entry = module->GetGearset((int)gearsetId); @@ -131,9 +132,10 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } + // Remove once internal is added. This no longer serves any purpose. private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); return ret; } @@ -216,18 +218,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private static EquipSlot GetSlot(uint slot) => slot switch { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - 2 => EquipSlot.Head, - 3 => EquipSlot.Body, - 4 => EquipSlot.Hands, - 6 => EquipSlot.Legs, - 7 => EquipSlot.Feet, - 8 => EquipSlot.Ears, - 9 => EquipSlot.Neck, + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + 2 => EquipSlot.Head, + 3 => EquipSlot.Body, + 4 => EquipSlot.Hands, + 6 => EquipSlot.Legs, + 7 => EquipSlot.Feet, + 8 => EquipSlot.Ears, + 9 => EquipSlot.Neck, 10 => EquipSlot.Wrists, 11 => EquipSlot.RFinger, 12 => EquipSlot.LFinger, - _ => EquipSlot.Unknown, + _ => EquipSlot.Unknown, }; } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 7e5cf59..9ee8d8f 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -8,12 +8,11 @@ using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; + namespace Glamourer.Interop; -/// -/// This struct is the struct that loadallequipment passes in as its gearsetData container. -/// -[StructLayout(LayoutKind.Explicit)] // Size of 70 bytes maybe? +// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +[StructLayout(LayoutKind.Explicit)] public readonly struct GearsetItemDataStruct { // Stores the weapon data. Includes both dyes in the data. @@ -54,30 +53,15 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + // The above can be removed after the FFXIVClientStruct Merge is made! + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; - - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) - { - // Let the gearset data process all of its loads and slot flag update calls first. - var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - // Ensure that the owner of the drawdata container is a character base. - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); - return ret; - } - public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -150,7 +134,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -160,7 +144,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -173,6 +157,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + { + // Let the gearset data process all of its loads and slot flag update calls first. + var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); + Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; + if (!ownerDrawObject.IsCharacterBase) + return ret; + + // invoke the changed event for the state listener and return. + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); + return ret; + } // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) @@ -183,7 +181,6 @@ public unsafe class UpdateSlotService : IDisposable $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; - // Iterate through offsets from 20 to 60 and format the CharacterArmor data for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index e1bd6a4..891c61d 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -54,7 +54,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } @@ -67,7 +67,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, new EntireCustomizeTransaction(applied, old, customizeInput)); @@ -78,10 +78,7 @@ public class StateEditor( { var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) - { - Glamourer.Log.Information("Not Setting State or invoking, Editor requested us not to change it!"); return; - } var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; var actors = type is StateChangeType.Equip @@ -92,8 +89,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, null, settings); - Glamourer.Log.Debug( - $"[ChangeItem] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { @@ -122,8 +119,8 @@ public class StateEditor( return; var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( - $"[ChangeBonus] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } @@ -155,8 +152,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, stains, settings); - Glamourer.Log.Debug( - $"[ChangeEquip] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); @@ -187,7 +184,7 @@ public class StateEditor( return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } @@ -419,8 +416,8 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - - Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + + Glamourer.Log.Verbose($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 4d10c49..d312815 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,8 +13,8 @@ using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; using Glamourer.Api.Enums; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 129f8bb..0348148 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -274,7 +274,7 @@ public sealed class StateManager( if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. @@ -302,7 +302,7 @@ public sealed class StateManager( state.Materials.Clear(); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) @@ -453,22 +453,22 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, isUpdate); + ReapplyState(actor, state, forceRedraw, source, stateUpdate); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(isUpdate) + if(stateUpdate) StateUpdated.Invoke(StateUpdateType.Reapply, data); } From 9c57935a872dc98fa0ddfdcc81d891dada3b0054 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 18:52:16 -0800 Subject: [PATCH 008/212] removed unessisary usings and corrected gearsetDataLoaded stateListener ref. --- Glamourer/Automation/AutoDesignApplier.cs | 1 - Glamourer/Events/GearsetDataLoaded.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 52956cc..660acf4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,5 +1,4 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Designs.Links; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 680ae3f..dd12bc1 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -15,7 +15,7 @@ public sealed class GearsetDataLoaded() { public enum Priority { - /// + /// StateListener = 0, } } \ No newline at end of file From 1d185e9bfe8f9badb6b58c62f6cfda49bcefba8a Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:07:43 -0800 Subject: [PATCH 009/212] Added Proper Unsubsribe from OnStateUpdated. Ensures that ReapplyAutomation is called from OnAutomationChange when change occurs. Reformatted temporary Signature locations and comments to align with the structure of the respective classes. --- Glamourer/Api/StateApi.cs | 1 + Glamourer/Automation/AutoDesignApplier.cs | 4 +-- Glamourer/Interop/InventoryService.cs | 35 ++++++------------ Glamourer/Interop/UpdateSlotService.cs | 44 +++++++++++++---------- 4 files changed, 39 insertions(+), 45 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 248e294..34f4ad9 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -54,6 +54,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); + _stateUpdated.Unsubscribe(OnStateUpdated); _gPose.Unsubscribe(OnGPoseChange); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 660acf4..1655c15 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -163,7 +163,7 @@ public sealed class AutoDesignApplier : IDisposable { Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); foreach (var actor in data.Objects) - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -174,7 +174,7 @@ public sealed class AutoDesignApplier : IDisposable if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } } diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 33ba5cf..6c4a223 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -13,18 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - // Called by EquipGearset, but returns a pointer instead of an int. - // This is the internal function processed by all sources of Equipping a gearset, - // such as hotbar gearset application and command gearset application - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; - private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; - - // The following above is currently pending for an accepted PR in FFXIVCLientStructs. - // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. - private readonly MovedEquipment _movedItemsEvent; private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); @@ -34,24 +22,29 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) + //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); + + // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] interop.InitializeFromAttributes(this); _moveItemHook.Enable(); - _equipGearsetHook.Enable(); _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetHook.Dispose(); _equipGearsetInternalHook.Dispose(); } - private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); + // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. + // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook; + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { @@ -132,14 +125,6 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } - // Remove once internal is added. This no longer serves any purpose. - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) - { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); - return ret; - } - private static uint FixId(uint itemId) => itemId % 50000; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 9ee8d8f..5f6e9cb 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -11,9 +11,9 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files [StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetItemDataStruct +public readonly struct GearsetDataStruct { // Stores the weapon data. Includes both dyes in the data. [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; @@ -53,11 +53,6 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - // The above can be removed after the FFXIVClientStruct Merge is made! - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; @@ -68,9 +63,12 @@ public unsafe class UpdateSlotService : IDisposable EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + + // Usable after the merge with client structs. + //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -129,6 +127,19 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; + // This signature is what calls the weapon/equipment/crest load functions in the drawData container inherited from a human/characterBase. + // + // Contrary to assumption, this is not frequently fired when any slot changes, and is instead only called when another player + // initially loads, or when the client player changes gearsets. (Does not fire when another player or self is redrawn) + // + // This functions purpose is to iterate all Equipment/Weapon/Crest data on gearset change / initial player load, and determine which slots need to fire FlagSlotForUpdate. + // + // Because Glamourer processes GameState changes by detouring this method, this means by returning original after detour, any logic performed after will occur + // AFTER Glamourer finishes applying all changes to the game State, providing a gearset endpoint. (MetaData not included) + // Currently pending a merge to clientStructs, after which it can be removed, along with the explicit struct. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData); + [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] private readonly Hook _loadGearsetDataHook = null!; @@ -157,23 +168,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) { // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + Model drawObject = drawDataContainer->OwnerObject->DrawObject; + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); + GearsetDataLoadedEvent.Invoke(drawObject); + // Can use for debugging, if desired. // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. - private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) + // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. + private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + From 1cd8e5fb7ec29097aaa2e413fce01a8aadb7a0cf Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:35:10 -0800 Subject: [PATCH 010/212] Corrected comments on StateApi for PR --- Glamourer/Api/StateApi.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 34f4ad9..cb7fe51 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -340,9 +340,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - // Remove this comment before creating PR. - Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); - + // Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); @@ -354,13 +352,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateUpdated(StateUpdateType type, ActorData actors) { + // Glamourer.Log.Verbose($"[OnStateUpdated] Sending out OnStateUpdated with type {type}."); if (StateUpdated != null) foreach (var actor in actors.Objects) - { - // Remove these before creating PR - Glamourer.Log.Information($"[ENDPOINT DEBUGGING] 0x{actor.Address:X} had update of type {type}."); - Glamourer.Log.Information("--------------------------------------------------------------"); StateUpdated.Invoke(actor.Address, type); - } } } From ebdfd3f8bc56dcd7a12f4cb3e9f84164e8dd946e Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:50:34 -0800 Subject: [PATCH 011/212] Correct spacing and formatting further to align with main Glamourer branch preferences. --- Glamourer/Interop/InventoryService.cs | 71 ++++++++++---------- Glamourer/Interop/UpdateSlotService.cs | 91 +++++++++++++------------- Glamourer/State/StateEditor.cs | 5 +- 3 files changed, 83 insertions(+), 84 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6c4a223..f0ed6b5 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -13,9 +13,10 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; @@ -80,18 +81,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); - Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); - Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); - Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); - Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); - Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); - Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); - Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); - Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); - Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); - Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); - Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { @@ -106,17 +107,17 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } Add(EquipSlot.MainHand, ref entry->Items[0]); - Add(EquipSlot.OffHand, ref entry->Items[1]); - Add(EquipSlot.Head, ref entry->Items[2]); - Add(EquipSlot.Body, ref entry->Items[3]); - Add(EquipSlot.Hands, ref entry->Items[5]); - Add(EquipSlot.Legs, ref entry->Items[6]); - Add(EquipSlot.Feet, ref entry->Items[7]); - Add(EquipSlot.Ears, ref entry->Items[8]); - Add(EquipSlot.Neck, ref entry->Items[9]); - Add(EquipSlot.Wrists, ref entry->Items[10]); - Add(EquipSlot.RFinger, ref entry->Items[11]); - Add(EquipSlot.LFinger, ref entry->Items[12]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -203,18 +204,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private static EquipSlot GetSlot(uint slot) => slot switch { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - 2 => EquipSlot.Head, - 3 => EquipSlot.Body, - 4 => EquipSlot.Hands, - 6 => EquipSlot.Legs, - 7 => EquipSlot.Feet, - 8 => EquipSlot.Ears, - 9 => EquipSlot.Neck, + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + 2 => EquipSlot.Head, + 3 => EquipSlot.Body, + 4 => EquipSlot.Hands, + 6 => EquipSlot.Legs, + 7 => EquipSlot.Feet, + 8 => EquipSlot.Ears, + 9 => EquipSlot.Neck, 10 => EquipSlot.Wrists, 11 => EquipSlot.RFinger, 12 => EquipSlot.LFinger, - _ => EquipSlot.Unknown, + _ => EquipSlot.Unknown, }; } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 5f6e9cb..e453c6e 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -11,46 +11,6 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files -[StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetDataStruct -{ - // Stores the weapon data. Includes both dyes in the data. - [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; - [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; - - [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 - [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - - // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. - [FieldOffset(18)] public readonly byte UNK_18; - [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. - - // Legacy helmet equip slot armor for a character. - [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; - [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; - [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; - [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; - [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; - [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; - [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; - [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; - [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; - [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; - - // Byte array of all slot's secondary dyes. - [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; - [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; - [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; - [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; - [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; - [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; - [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; - [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; - [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; - [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; -} - public unsafe class UpdateSlotService : IDisposable { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; @@ -145,22 +105,20 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; + return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; + return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) @@ -175,7 +133,6 @@ public unsafe class UpdateSlotService : IDisposable Model drawObject = drawDataContainer->OwnerObject->DrawObject; Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); GearsetDataLoadedEvent.Invoke(drawObject); - // Can use for debugging, if desired. // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } @@ -199,3 +156,43 @@ public unsafe class UpdateSlotService : IDisposable return ret; } } + +// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files +[StructLayout(LayoutKind.Explicit)] +public readonly struct GearsetDataStruct +{ + // Stores the weapon data. Includes both dyes in the data. + [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; + [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; + + [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 + [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. + + // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + [FieldOffset(18)] public readonly byte UNK_18; + [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. + + // Legacy helmet equip slot armor for a character. + [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; + [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; + [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; + [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; + [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; + [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; + [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; + [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; + [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; + [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; + + // Byte array of all slot's secondary dyes. + [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; + [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; + [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; + [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; + [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; + [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; + [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; + [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; + [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; + [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; +} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 891c61d..b122352 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -253,7 +253,7 @@ public class StateEditor( return; var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } @@ -417,7 +417,8 @@ public class StateEditor( ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - Glamourer.Log.Verbose($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); From 8add6e5519c6998194ad4adc5f818704e5fa96d6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:37:21 +0100 Subject: [PATCH 012/212] Fix some .net update issues. --- Glamourer/Gui/Customization/CustomizeParameterDrawData.cs | 4 ++-- Glamourer/Gui/Equipment/BonusDrawData.cs | 4 ++-- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 ++-- Penumbra.GameData | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index aa43b79..421caed 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -6,8 +6,8 @@ namespace Glamourer.Gui.Customization; public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly CustomizeParameterFlag Flag = flag; public bool Locked; public bool DisplayApplication; diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index 9c023b8..e19287a 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -7,8 +7,8 @@ namespace Glamourer.Gui.Equipment; public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly BonusItemFlag Slot = slot; public bool Locked; public bool DisplayApplication; diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 9a3142b..b72e234 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -7,8 +7,8 @@ namespace Glamourer.Gui.Equipment; public struct EquipDrawData(EquipSlot slot, in DesignData designData) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly EquipSlot Slot = slot; public bool Locked; public bool DisplayApplication; diff --git a/Penumbra.GameData b/Penumbra.GameData index 63ffca0..c525072 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 63ffca0ff0ad626605120e58809c888d92053d64 +Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6 From cdc67a035c5efdc04801b8630e799ad59ea742f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:37:59 +0100 Subject: [PATCH 013/212] Check for ENPC player copies and treat them as transformations. --- Glamourer/State/StateListener.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d054a25..651e63e 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -9,6 +9,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; @@ -50,6 +51,7 @@ public class StateListener : IDisposable private readonly Dictionary _fistOffhands = []; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; + private bool _isPlayerNpc; private ActorState? _creatingState; private ActorState? _customizeState; @@ -117,11 +119,13 @@ public class StateListener : IDisposable return; _creatingIdentifier = actor.GetIdentifier(_actors); - ref var modelId = ref *(uint*)modelPtr; ref var customize = ref *(CustomizeArray*)customizePtr; if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState)) { + _isPlayerNpc = _creatingIdentifier.Type is IdentifierType.Player + && actor.IsCharacter + && actor.AsCharacter->GetObjectKind() is ObjectKind.EventNpc; switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr)) { // TODO handle right @@ -327,7 +331,7 @@ public class StateListener : IDisposable && weapon.Weapon.Id != 0 && _fistOffhands.TryGetValue(actor, out var lastFistOffhand)) { - Glamourer.Log.Information($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); weapon = lastFistOffhand; } @@ -420,15 +424,22 @@ public class StateListener : IDisposable } var baseData = state.BaseData.Armor(slot); - var change = UpdateState.NoChange; + + var change = UpdateState.NoChange; if (baseData.Stains != armor.Stains) { + if (_isPlayerNpc) + return UpdateState.Transformed; + state.BaseData.SetStain(slot, armor.Stains); change = UpdateState.Change; } if (baseData.Set.Id != armor.Set.Id || baseData.Variant != armor.Variant && !fistWeapon) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var item = _items.Identify(slot, armor.Set, armor.Variant); state.BaseData.SetItem(slot, item); change = UpdateState.Change; @@ -460,6 +471,9 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; if (baseData.Id != actorItem.Id || baseData.PrimaryId != item.Set || baseData.Variant != item.Variant) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var identified = _items.Identify(slot, item.Set, item.Variant); state.BaseData.SetBonusItem(slot, identified); change = UpdateState.Change; @@ -576,6 +590,9 @@ public class StateListener : IDisposable if (baseData.Skeleton.Id != weapon.Skeleton.Id || baseData.Weapon.Id != weapon.Weapon.Id || baseData.Variant != weapon.Variant) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var item = _items.Identify(slot, weapon.Skeleton, weapon.Weapon, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.MainhandType : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); @@ -623,6 +640,10 @@ public class StateListener : IDisposable if (checkTransform && !actor.Customize->Equals(customize)) return UpdateState.Transformed; + // Check for player NPCs with a different game state. + if (_isPlayerNpc && !actor.Customize->Equals(state.BaseData.Customize)) + return UpdateState.Transformed; + // Customize array did not change to stored state. if (state.BaseData.Customize.Equals(customize)) return UpdateState.NoChange; // TODO: handle wrong base data. From 4db08224430ad4405a67b03c83e168b46841e173 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:40:08 +0100 Subject: [PATCH 014/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index c525072..5bac66e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6 +Subproject commit 5bac66e5ad73e57919aff7f8b046606b75e191a2 From 96dca5dbe7f4a61086b0f192521a60c3949372d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 17:47:36 +0100 Subject: [PATCH 015/212] Cherry-Pick from ebdfd3f8 to use EquipGearSetInternal. --- Glamourer/Interop/InventoryService.cs | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6d8e58b..f0ed6b5 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -1,5 +1,6 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; @@ -19,36 +20,43 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = - interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) + //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); + + // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] + interop.InitializeFromAttributes(this); _moveItemHook.Enable(); - _equipGearsetHook.Enable(); + _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetHook.Dispose(); + _equipGearsetInternalHook.Dispose(); } - private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); + // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. + // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook; + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset(gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { - var entry = module->GetGearset(gearsetId); + var entry = module->GetGearset((int)gearsetId); if (entry == null) return ret; From f1b335e794fe8e4bd72f6b08ba9b492c2dac6efc Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 20 Jan 2025 16:49:52 +0000 Subject: [PATCH 016/212] [CI] Updating repo.json for testing_1.3.5.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index dab02ea..5f4ae31 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.2", + "TestingAssemblyVersion": "1.3.5.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c43ce9d978f57f10f62f8c981533a5344cd4b638 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Tue, 21 Jan 2025 10:47:12 -0800 Subject: [PATCH 017/212] Updated new functionality and internal gearset to use FFXIVClientStructs member functions and implemented structs, corrected remaining spacing issues. --- Glamourer/Api/StateApi.cs | 5 +- Glamourer/Events/GearsetDataLoaded.cs | 8 +-- Glamourer/Interop/InventoryService.cs | 20 ++---- Glamourer/Interop/UpdateSlotService.cs | 89 ++++++-------------------- Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateListener.cs | 3 +- 6 files changed, 33 insertions(+), 94 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index cb7fe51..41f7650 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -14,7 +14,6 @@ using StateChanged = Glamourer.Events.StateChanged; using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; - public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -340,7 +339,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - // Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); + Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]"); if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); @@ -352,7 +351,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateUpdated(StateUpdateType type, ActorData actors) { - // Glamourer.Log.Verbose($"[OnStateUpdated] Sending out OnStateUpdated with type {type}."); + Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateUpdated != null) foreach (var actor in actors.Objects) StateUpdated.Invoke(actor.Address, type); diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index dd12bc1..4750939 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -4,10 +4,10 @@ using Penumbra.GameData.Interop; namespace Glamourer.Events; /// -/// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. -/// This defines a universal endpoint of base game state application to monitor. +/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) +/// This defines an endpoint for when the gameState is updated. /// -/// The model drawobject associated with the finished load (Also fired by other players on render) +/// The model draw object associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() @@ -18,4 +18,4 @@ public sealed class GearsetDataLoaded() /// StateListener = 0, } -} \ No newline at end of file +} diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f0ed6b5..4b98d46 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) - //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); - - // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] - interop.InitializeFromAttributes(this); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); _moveItemHook.Enable(); - _equipGearsetInternalHook.Enable(); + _equipGearsetHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetInternalHook.Dispose(); + _equipGearsetHook.Dispose(); } - // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. - // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; + private readonly Hook _equipGearsetHook = null!; - private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) + private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index e453c6e..466f1ae 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Network; using Glamourer.Events; using Penumbra.GameData; using Penumbra.GameData.DataContainers; @@ -13,22 +14,20 @@ namespace Glamourer.Interop; public unsafe class UpdateSlotService : IDisposable { - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; - public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - public readonly GearsetDataLoaded GearsetDataLoadedEvent; - private readonly DictBonusItems _bonusItems; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + public readonly GearsetDataLoaded GearsetDataLoadedEvent; + private readonly DictBonusItems _bonusItems; public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + _bonusItems = bonusItems; - // Usable after the merge with client structs. - //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); - _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -87,25 +86,15 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; - // This signature is what calls the weapon/equipment/crest load functions in the drawData container inherited from a human/characterBase. - // - // Contrary to assumption, this is not frequently fired when any slot changes, and is instead only called when another player - // initially loads, or when the client player changes gearsets. (Does not fire when another player or self is redrawn) - // - // This functions purpose is to iterate all Equipment/Weapon/Crest data on gearset change / initial player load, and determine which slots need to fire FlagSlotForUpdate. - // - // Because Glamourer processes GameState changes by detouring this method, this means by returning original after detour, any logic performed after will occur - // AFTER Glamourer finishes applying all changes to the game State, providing a gearset endpoint. (MetaData not included) - // Currently pending a merge to clientStructs, after which it can be removed, along with the explicit struct. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData); - - [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] + /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). + /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. + /// + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); private readonly Hook _loadGearsetDataHook = null!; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -114,7 +103,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -123,29 +112,27 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) { - Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { - // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); Model drawObject = drawDataContainer->OwnerObject->DrawObject; - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) + private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + - $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); @@ -156,43 +143,3 @@ public unsafe class UpdateSlotService : IDisposable return ret; } } - -// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files -[StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetDataStruct -{ - // Stores the weapon data. Includes both dyes in the data. - [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; - [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; - - [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 - [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - - // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. - [FieldOffset(18)] public readonly byte UNK_18; - [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. - - // Legacy helmet equip slot armor for a character. - [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; - [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; - [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; - [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; - [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; - [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; - [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; - [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; - [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; - [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; - - // Byte array of all slot's secondary dyes. - [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; - [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; - [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; - [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; - [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; - [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; - [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; - [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; - [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; - [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; -} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b122352..13b0706 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -416,7 +416,7 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - + Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d570805..c4c16b5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -225,7 +225,8 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) + if (actor.Identifier(_actors, out var identifier) + && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); locked = state.Sources[slot, false] is StateSource.IpcFixed; From 70918e539379871eb256a21ebad833d13315031d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:04:22 +0100 Subject: [PATCH 018/212] Add AutoDesignSet toggle for resetting settings. --- Glamourer/Automation/AutoDesignManager.cs | 16 +++++ Glamourer/Events/AutomationChanged.cs | 3 + Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 71 ++++++++++++-------- 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 5d30de0..6635a89 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -234,6 +234,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); } + public void ChangeResetSettings(int whichSet, bool newValue) + { + if (whichSet >= _data.Count || whichSet < 0) + return; + + var set = _data[whichSet]; + if (newValue == set.ResetTemporarySettings) + return; + + var old = set.ResetTemporarySettings; + set.ResetTemporarySettings = newValue; + Save(); + Glamourer.Log.Debug($"Changed resetting of temporary settings of set {whichSet + 1} from {old} to {newValue}."); + _event.Invoke(AutomationChanged.Type.ChangedTemporarySettingsReset, set, newValue); + } + public void AddDesign(AutoDesignSet set, IDesignStandIn design) { var newDesign = new AutoDesign() diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 26f799a..c368899 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -37,6 +37,9 @@ public sealed class AutomationChanged() /// Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. ChangedBase, + /// Change the resetting of temporary settings for a given set. Additional data is the new value. + ChangedTemporarySettingsReset, + /// Add a new associated design to a given set. Additional data is the index it got added at [int]. AddedDesign, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 924f822..caa7d60 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -58,38 +58,53 @@ public class SetPanel( var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (ImUtf8.Group()) { - var enabled = Selection.Enabled; - if (ImGui.Checkbox("##Enabled", ref enabled)) - _manager.SetState(_selector.SelectionIndex, enabled); - ImGuiUtil.LabeledHelpMarker("Enabled", - "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); - } - - ImGui.SameLine(); - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) - { - var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; - if (ImGui.Checkbox("##gameState", ref useGame)) - _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); - ImGuiUtil.LabeledHelpMarker("Use Game State as Base", - "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " - + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); - } - - ImGui.SameLine(); - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) - { - var editing = _config.ShowAutomationSetEditing; - if (ImGui.Checkbox("##Show Editing", ref editing)) + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { - _config.ShowAutomationSetEditing = editing; - _config.Save(); + var enabled = Selection.Enabled; + if (ImGui.Checkbox("##Enabled", ref enabled)) + _manager.SetState(_selector.SelectionIndex, enabled); + ImGuiUtil.LabeledHelpMarker("Enabled", + "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); } - ImGuiUtil.LabeledHelpMarker("Show Editing", - "Show options to change the name or the associated character or NPC of this design set."); + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; + if (ImGui.Checkbox("##gameState", ref useGame)) + _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); + ImGuiUtil.LabeledHelpMarker("Use Game State as Base", + "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " + + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); + } + } + + ImGui.SameLine(); + using (ImUtf8.Group()) + { + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var editing = _config.ShowAutomationSetEditing; + if (ImGui.Checkbox("##Show Editing", ref editing)) + { + _config.ShowAutomationSetEditing = editing; + _config.Save(); + } + + ImGuiUtil.LabeledHelpMarker("Show Editing", + "Show options to change the name or the associated character or NPC of this design set."); + } + + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var resetSettings = _selector.Selection!.ResetTemporarySettings; + if (ImGui.Checkbox("##resetSettings", ref resetSettings)) + _manager.ChangeResetSettings(_selector.SelectionIndex, resetSettings); + + ImGuiUtil.LabeledHelpMarker("Reset Temporary Settings", + "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."); + } } if (_config.ShowAutomationSetEditing) From 30468e0b09e892085c5d788e1e89d41dcadc927b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:04:30 +0100 Subject: [PATCH 019/212] Fix checkbox not working. --- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 5eda7cc..021a396 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -201,7 +201,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImGui.TableNextColumn(); var inherit = settings.ForceInherit; - if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref inherit)) + if (TwoStateCheckbox.Instance.Draw("##ForceInherit"u8, ref inherit)) updatedMod = (mod, settings with { ForceInherit = inherit }); ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8); ImGui.TableNextColumn(); From 29166693193cee2201a069c0589475627ef0617a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:06:29 +0100 Subject: [PATCH 020/212] Change EquipGearsetInternal to CS. --- Glamourer/Interop/InventoryService.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f0ed6b5..4b98d46 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) - //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); - - // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] - interop.InitializeFromAttributes(this); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); _moveItemHook.Enable(); - _equipGearsetInternalHook.Enable(); + _equipGearsetHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetInternalHook.Dispose(); + _equipGearsetHook.Dispose(); } - // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. - // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; + private readonly Hook _equipGearsetHook = null!; - private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) + private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); From 7be283ca30629e648725e4e1861616d86eda7238 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 16:46:10 +0100 Subject: [PATCH 021/212] Apply API renames. --- Glamourer.Api | 2 +- Glamourer/Api/IpcProviders.cs | 2 +- Glamourer/Api/StateApi.cs | 8 ++++---- Glamourer/Events/StateUpdated.cs | 2 +- Glamourer/State/StateEditor.cs | 4 ++-- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 12 ++++++------ 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index b1b90e6..4ac38fb 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit b1b90e6ecfeee76a12cb27793753fa87af21083f +Subproject commit 4ac38fbed6fb0f31c0b75de26950ab82d3bee258 diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 166245f..515cd34 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -52,7 +52,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), - IpcSubscribers.StateUpdated.Provider(pi, api.State), + IpcSubscribers.StateFinalized.Provider(pi, api.State), IpcSubscribers.GPoseChanged.Provider(pi, api.State), ]; _initializedProvider.Invoke(); diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 41f7650..347d2b6 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -255,7 +255,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public event Action? StateChanged; public event Action? StateChangedWithType; - public event Action? StateUpdated; + public event Action? StateFinalized; public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) @@ -349,11 +349,11 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable StateChangedWithType.Invoke(actor.Address, type); } - private void OnStateUpdated(StateUpdateType type, ActorData actors) + private void OnStateUpdated(StateFinalizationType type, ActorData actors) { Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateUpdated != null) + if (StateFinalized != null) foreach (var actor in actors.Objects) - StateUpdated.Invoke(actor.Address, type); + StateFinalized.Invoke(actor.Address, type); } } diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs index f18a69a..faaf33a 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateUpdated.cs @@ -14,7 +14,7 @@ namespace Glamourer.Events; /// /// public sealed class StateUpdated() - : EventWrapper(nameof(StateUpdated)) + : EventWrapper(nameof(StateUpdated)) { public enum Priority { diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 13b0706..ebf347f 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -43,7 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateUpdated.Invoke(StateUpdateType.ModelChange, actors); + StateUpdated.Invoke(StateFinalizationType.ModelChange, actors); } /// @@ -421,7 +421,7 @@ public class StateEditor( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) - StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); + StateUpdated.Invoke(StateFinalizationType.DesignApplied, actors); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index c4c16b5..d8648bb 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -281,7 +281,7 @@ public class StateListener : IDisposable _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateUpdated.Invoke(StateUpdateType.Gearset, actors); + _stateUpdated.Invoke(StateFinalizationType.Gearset, actors); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 0348148..948e225 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -279,7 +279,7 @@ public sealed class StateManager( StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. if(stateUpdate) - StateUpdated.Invoke(StateUpdateType.Revert, actors); + StateUpdated.Invoke(StateFinalizationType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -306,7 +306,7 @@ public sealed class StateManager( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertAdvanced, actors); + StateUpdated.Invoke(StateFinalizationType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -325,7 +325,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertCustomize, actors); + StateUpdated.Invoke(StateFinalizationType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -376,7 +376,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertEquipment, actors); + StateUpdated.Invoke(StateFinalizationType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -469,7 +469,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); if(stateUpdate) - StateUpdated.Invoke(StateUpdateType.Reapply, data); + StateUpdated.Invoke(StateFinalizationType.Reapply, data); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -490,7 +490,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); // invoke the automation update based on what reset is. - StateUpdated.Invoke(wasReset ? StateUpdateType.RevertAutomation : StateUpdateType.ReapplyAutomation, data); + StateUpdated.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From d9f9937d41390b34eab9d151e08f5b0e5d983789 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 17:50:58 +0100 Subject: [PATCH 022/212] Continue renames, some cleanup. --- Glamourer/Api/DesignsApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 - Glamourer/Api/StateApi.cs | 40 +++---- Glamourer/Designs/IDesignEditor.cs | 8 +- Glamourer/Events/GPoseService.cs | 4 +- .../{StateUpdated.cs => StateFinalized.cs} | 13 +-- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 + .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 107 ++++++++++++------ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Interop/UpdateSlotService.cs | 29 ++--- Glamourer/Services/CommandService.cs | 4 +- Glamourer/State/StateEditor.cs | 27 ++--- Glamourer/State/StateListener.cs | 8 +- Glamourer/State/StateManager.cs | 42 +++---- 18 files changed, 175 insertions(+), 134 deletions(-) rename Glamourer/Events/{StateUpdated.cs => StateFinalized.cs} (53%) diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 6c3037e..08f5ddc 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -33,7 +33,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); using var restrict = ApiHelpers.Restrict(design, flags); stateManager.ApplyDesign(state, design, settings); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 515cd34..704f008 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin; using Glamourer.Api.Api; using Glamourer.Api.Helpers; using OtterGui.Services; -using System.Reflection.Emit; using Glamourer.Api.Enums; namespace Glamourer.Api; diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 347d2b6..eaf9d01 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -11,9 +11,9 @@ using OtterGui.Services; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; -using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; + public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -23,7 +23,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly AutoDesignApplier _autoDesigns; private readonly ObjectManager _objects; private readonly StateChanged _stateChanged; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, @@ -33,27 +33,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable AutoDesignApplier autoDesigns, ObjectManager objects, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, GPoseService gPose) { - _helpers = helpers; - _stateManager = stateManager; - _converter = converter; - _config = config; - _autoDesigns = autoDesigns; - _objects = objects; - _stateChanged = stateChanged; - _stateUpdated = stateUpdated; - _gPose = gPose; + _helpers = helpers; + _stateManager = stateManager; + _converter = converter; + _config = config; + _autoDesigns = autoDesigns; + _objects = objects; + _stateChanged = stateChanged; + _stateFinalized = stateFinalized; + _gPose = gPose; _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); - _stateUpdated.Subscribe(OnStateUpdated, Events.StateUpdated.Priority.GlamourerIpc); - _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); + _stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi); + _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi); } public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); - _stateUpdated.Unsubscribe(OnStateUpdated); + _stateFinalized.Unsubscribe(OnStateFinalized); _gPose.Unsubscribe(OnGPoseChange); } @@ -253,16 +253,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public event Action? StateChanged; - public event Action? StateChangedWithType; + public event Action? StateChanged; + public event Action? StateChangedWithType; public event Action? StateFinalized; - public event Action? GPoseChanged; + public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); _stateManager.ApplyDesign(state, design, settings); ApiHelpers.Lock(state, key, flags); } @@ -349,7 +349,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable StateChangedWithType.Invoke(actor.Address, type); } - private void OnStateUpdated(StateFinalizationType type, ActorData actors) + private void OnStateFinalized(StateFinalizationType type, ActorData actors) { Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateFinalized != null) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 0178620..c18c98b 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -14,7 +14,7 @@ public readonly record struct ApplySettings( bool UseSingleSource = false, bool MergeLinks = false, bool ResetMaterials = false, - bool SendStateUpdate = false) + bool IsFinal = false) { public static readonly ApplySettings Manual = new() { @@ -25,7 +25,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -37,7 +37,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = true, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings Game = new() @@ -49,7 +49,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = true, - SendStateUpdate = false, + IsFinal = false, }; } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index a84f1d6..44421a0 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -13,8 +13,8 @@ public sealed class GPoseService : EventWrapper public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } public bool InGPose { get; private set; } diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateFinalized.cs similarity index 53% rename from Glamourer/Events/StateUpdated.cs rename to Glamourer/Events/StateFinalized.cs index faaf33a..e8548e9 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -1,24 +1,23 @@ +using Glamourer.Api; using Glamourer.Api.Enums; -using Glamourer.Designs.History; using Glamourer.Interop.Structs; -using Glamourer.State; using OtterGui.Classes; namespace Glamourer.Events; /// -/// Triggered when a Design is edited in any way. +/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. /// /// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. /// /// -public sealed class StateUpdated() - : EventWrapper(nameof(StateUpdated)) +public sealed class StateFinalized() + : EventWrapper(nameof(StateFinalized)) { public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } } diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index bdc345f..50f86fd 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -183,7 +183,7 @@ public sealed class DesignQuickBar : Window, IDisposable } using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } private void DrawRevertButton(Vector2 buttonSize) @@ -213,7 +213,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetState(state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(state!, StateSource.Manual, isFinal: true); } private void DrawRevertAutomationButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index ced78fb..d8f3cd1 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -385,7 +385,7 @@ public class ActorPanel { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(_state!, StateSource.Manual, isFinal: true); ImGui.SameLine(); @@ -423,7 +423,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } private void DrawApplyToTarget() @@ -440,7 +440,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } @@ -467,7 +467,7 @@ public class ActorPanel var text = ImGui.GetClipboardText(); var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index c44a722..62f93e9 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -79,7 +79,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); - _state.ApplyDesign(state!, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); } using (ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 8f561af..1a78b24 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -51,6 +51,7 @@ public class IpcTesterPanel( Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); state.GPoseChanged.Enable(); state.StateChanged.Enable(); + state.StateFinalized.Enable(); framework.Update += CheckUnsubscribe; _subscribed = true; } @@ -73,5 +74,6 @@ public class IpcTesterPanel( _subscribed = false; state.GPoseChanged.Disable(); state.StateChanged.Disable(); + state.StateFinalized.Disable(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index f378625..638bffc 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.GameData.Interop; using Penumbra.String; @@ -31,9 +32,16 @@ public class StateIpcTester : IUiService, IDisposable private string? _getStateString; public readonly EventSubscriber StateChanged; - private nint _lastStateChangeActor; - private ByteString _lastStateChangeName = ByteString.Empty; - private DateTime _lastStateChangeTime; + private nint _lastStateChangeActor; + private ByteString _lastStateChangeName = ByteString.Empty; + private DateTime _lastStateChangeTime; + private StateChangeType _lastStateChangeType; + + public readonly EventSubscriber StateFinalized; + private nint _lastStateFinalizeActor; + private ByteString _lastStateFinalizeName = ByteString.Empty; + private DateTime _lastStateFinalizeTime; + private StateFinalizationType _lastStateFinalizeType; public readonly EventSubscriber GPoseChanged; private bool _lastGPoseChangeValue; @@ -44,15 +52,18 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(IDalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; - StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); StateChanged.Disable(); + StateFinalized.Disable(); GPoseChanged.Disable(); } public void Dispose() { StateChanged.Dispose(); + StateFinalized.Dispose(); GPoseChanged.Dispose(); } @@ -73,86 +84,88 @@ public class StateIpcTester : IUiService, IDisposable IpcTesterHelpers.DrawIntro("Last Error"); ImGui.TextUnformatted(_lastError.ToString()); IpcTesterHelpers.DrawIntro("Last State Change"); - PrintName(); + PrintChangeName(); + IpcTesterHelpers.DrawIntro("Last State Finalization"); + PrintFinalizeName(); IpcTesterHelpers.DrawIntro("Last GPose Change"); ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}"); IpcTesterHelpers.DrawIntro(GetState.Label); DrawStatePopup(); - if (ImGui.Button("Get##Idx")) + if (ImUtf8.Button("Get##Idx"u8)) { (_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateName.Label); - if (ImGui.Button("Get##Name")) + if (ImUtf8.Button("Get##Name"u8)) { (_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(ApplyState.Label); if (ImGuiUtil.DrawDisabledButton("Apply Last##Idx", Vector2.Zero, string.Empty, _state == null)) _lastError = new ApplyState(_pluginInterface).Invoke(_state!, _gameObjectIndex, _key, _flags); ImGui.SameLine(); - if (ImGui.Button("Apply Base64##Idx")) + if (ImUtf8.Button("Apply Base64##Idx"u8)) _lastError = new ApplyState(_pluginInterface).Invoke(_base64State, _gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(ApplyStateName.Label); if (ImGuiUtil.DrawDisabledButton("Apply Last##Name", Vector2.Zero, string.Empty, _state == null)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_state!, _gameObjectName, _key, _flags); ImGui.SameLine(); - if (ImGui.Button("Apply Base64##Name")) + if (ImUtf8.Button("Apply Base64##Name"u8)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(RevertState.Label); - if (ImGui.Button("Revert##Idx")) + if (ImUtf8.Button("Revert##Idx"u8)) _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertStateName.Label); - if (ImGui.Button("Revert##Name")) + if (ImUtf8.Button("Revert##Name"u8)) _lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(UnlockState.Label); - if (ImGui.Button("Unlock##Idx")) + if (ImUtf8.Button("Unlock##Idx"u8)) _lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key); IpcTesterHelpers.DrawIntro(UnlockStateName.Label); - if (ImGui.Button("Unlock##Name")) + if (ImUtf8.Button("Unlock##Name"u8)) _lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key); IpcTesterHelpers.DrawIntro(UnlockAll.Label); - if (ImGui.Button("Unlock##All")) + if (ImUtf8.Button("Unlock##All"u8)) _numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key); ImGui.SameLine(); ImGui.TextUnformatted($"Unlocked {_numUnlocked}"); IpcTesterHelpers.DrawIntro(RevertToAutomation.Label); - if (ImGui.Button("Revert##AutomationIdx")) + if (ImUtf8.Button("Revert##AutomationIdx"u8)) _lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label); - if (ImGui.Button("Revert##AutomationName")) + if (ImUtf8.Button("Revert##AutomationName"u8)) _lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); } @@ -162,44 +175,70 @@ public class StateIpcTester : IUiService, IDisposable if (_stateString == null) return; - using var p = ImRaii.Popup("State"); + using var p = ImUtf8.Popup("State"u8); if (!p) return; - if (ImGui.Button("Copy to Clipboard")) - ImGui.SetClipboardText(_stateString); + if (ImUtf8.Button("Copy to Clipboard"u8)) + ImUtf8.SetClipboardText(_stateString); if (_stateString[0] is '{') { ImGui.SameLine(); - if (ImGui.Button("Copy as Base64") && _state != null) - ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + if (ImUtf8.Button("Copy as Base64"u8) && _state != null) + ImUtf8.SetClipboardText(DesignConverter.ToBase64(_state)); } using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_stateString ?? string.Empty); + ImUtf8.TextWrapped(_stateString ?? string.Empty); - if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused()) ImGui.CloseCurrentPopup(); } - private unsafe void PrintName() + private unsafe void PrintChangeName() { - ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length); + ImUtf8.Text(_lastStateChangeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateChangeType})"); ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); + ImUtf8.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); } ImGui.SameLine(); - ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); + ImUtf8.Text($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); } - private void OnStateChanged(nint actor, StateChangeType _) + private unsafe void PrintFinalizeName() + { + ImUtf8.Text(_lastStateFinalizeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateFinalizeType})"); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImUtf8.CopyOnClickSelectable($"0x{_lastStateFinalizeActor:X}"); + } + + ImGui.SameLine(); + ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}"); + } + + private void OnStateChanged(nint actor, StateChangeType type) { _lastStateChangeActor = actor; _lastStateChangeTime = DateTime.UtcNow; _lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateChangeType = type; + } + + private void OnStateFinalized(nint actor, StateFinalizationType type) + { + _lastStateFinalizeActor = actor; + _lastStateFinalizeTime = DateTime.UtcNow; + _lastStateFinalizeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateFinalizeType = type; } private void OnGPoseChange(bool value) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index fe71609..42eb8e9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -460,7 +460,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -478,7 +478,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 345df11..aeb96f6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -196,7 +196,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } @@ -214,7 +214,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 466f1ae..ffa6581 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -18,6 +18,7 @@ public unsafe class UpdateSlotService : IDisposable public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -26,8 +27,8 @@ public unsafe class UpdateSlotService : IDisposable GearsetDataLoadedEvent = gearsetDataLoaded; _bonusItems = bonusItems; - _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -89,8 +90,8 @@ public unsafe class UpdateSlotService : IDisposable /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. /// - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); - private readonly Hook _loadGearsetDataHook = null!; + private delegate ulong LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); + private readonly Hook _loadGearsetDataHook; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { @@ -115,30 +116,30 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) + private ulong LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model drawObject = drawDataContainer->OwnerObject->DrawObject; + var drawObject = drawDataContainer->OwnerObject->DrawObject; GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) + + private static string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { - string ret = + var ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; - for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + for (var offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { - LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); - int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset - byte* dyePtr = (byte*)&gearsetData + dyeOffset; - ret += $"\nEquipSlot {((EquipSlot)(dyeOffset - 60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + var equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); + var dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + var dyePtr = (byte*)&gearsetData + dyeOffset; + ret += $"\nEquipSlot {(EquipSlot)(dyeOffset - 60)}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; } return ret; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index a869a09..98dfa19 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -668,7 +668,7 @@ public class CommandService : IDisposable, IApiService if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } else { @@ -677,7 +677,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ebf347f..42058d2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -17,7 +17,7 @@ public class StateEditor( InternalStateEditor editor, StateApplier applier, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, JobChangeState jobChange, Configuration config, ItemManager items, @@ -25,12 +25,12 @@ public class StateEditor( ModSettingApplier modApplier, GPoseService gPose) : IDesignEditor { - protected readonly InternalStateEditor Editor = editor; - protected readonly StateApplier Applier = applier; - protected readonly StateChanged StateChanged = stateChanged; - protected readonly StateUpdated StateUpdated = stateUpdated; - protected readonly Configuration Config = config; - protected readonly ItemManager Items = items; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateFinalized StateFinalized = stateFinalized; + protected readonly Configuration Config = config; + protected readonly ItemManager Items = items; /// Turn an actor to. public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, @@ -43,7 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateUpdated.Invoke(StateFinalizationType.ModelChange, actors); + StateFinalized.Invoke(StateFinalizationType.ModelChange, actors); } /// @@ -383,7 +383,7 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } - if (settings.ResetMaterials || (!settings.RespectManual && mergedDesign.ResetAdvancedDyes)) + if (settings.ResetMaterials || !settings.RespectManual && mergedDesign.ResetAdvancedDyes) state.Materials.Clear(); foreach (var (key, value) in mergedDesign.Design.Materials) @@ -420,8 +420,8 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later - if(settings.SendStateUpdate) - StateUpdated.Invoke(StateFinalizationType.DesignApplied, actors); + if (settings.IsFinal) + StateFinalized.Invoke(StateFinalizationType.DesignApplied, actors); return; @@ -442,7 +442,8 @@ public class StateEditor( if (!settings.MergeLinks || design is not Design d) merged = new MergedDesign(design); else - merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, + merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, + state.BaseData, false, Config.AlwaysApplyAssociatedMods); ApplyDesign(data, merged, settings with @@ -460,7 +461,7 @@ public class StateEditor( if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; - var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); // Do not change Shields to nothing. if (mh.Type is FullEquipType.Sword) return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d8648bb..3a6d6ef 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -41,7 +41,7 @@ public class StateListener : IDisposable private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -63,7 +63,7 @@ public class StateListener : IDisposable WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateUpdated stateUpdated) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) { _manager = manager; _items = items; @@ -88,7 +88,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; - _stateUpdated = stateUpdated; + _stateFinalized = stateFinalized; Subscribe(); } @@ -281,7 +281,7 @@ public class StateListener : IDisposable _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateUpdated.Invoke(StateFinalizationType.Gearset, actors); + _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 948e225..9b71586 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -18,20 +18,20 @@ using Penumbra.GameData.Interop; namespace Glamourer.State; public sealed class StateManager( - ActorManager _actors, + ActorManager actors, ItemManager items, - StateChanged @event, - StateUpdated @event2, + StateChanged changeEvent, + StateFinalized finalizeEvent, StateApplier applier, InternalStateEditor editor, - HumanModelList _humans, - IClientState _clientState, + HumanModelList humans, + IClientState clientState, Configuration config, JobChangeState jobChange, DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, @event2, jobChange, config, items, merger, modApplier, gPose), + : StateEditor(editor, applier, changeEvent, finalizeEvent, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -62,7 +62,7 @@ public sealed class StateManager( /// public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) - => GetOrCreate(actor.GetIdentifier(_actors), actor, out state); + => GetOrCreate(actor.GetIdentifier(actors), actor, out state); /// Try to obtain or create a new state for an existing actor. Returns false if no state could be created. public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) @@ -82,7 +82,7 @@ public sealed class StateManager( ModelData = FromActor(actor, true, false), BaseData = FromActor(actor, false, false), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), - LastTerritory = _clientState.TerritoryType, + LastTerritory = clientState.TerritoryType, }; // state.Identifier is owned. _states.Add(state.Identifier, state); @@ -115,7 +115,7 @@ public sealed class StateManager( // Model ID is only unambiguously contained in the game object. // The draw object only has the object type. // TODO reverse search model data to get model id from model. - if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) + if (!humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) { ret.LoadNonHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); @@ -236,7 +236,7 @@ public sealed class StateManager( public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - public void ResetState(ActorState state, StateSource source, uint key = 0, bool stateUpdate = false) + public void ResetState(ActorState state, StateSource source, uint key = 0, bool isFinal = false) { if (!state.Unlock(key)) return; @@ -278,8 +278,8 @@ public sealed class StateManager( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Revert, actors); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -306,7 +306,7 @@ public sealed class StateManager( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertAdvanced, actors); + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -325,7 +325,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertCustomize, actors); + StateFinalized.Invoke(StateFinalizationType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -376,7 +376,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertEquipment, actors); + StateFinalized.Invoke(StateFinalizationType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -453,23 +453,23 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isFinal = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, stateUpdate); + ReapplyState(actor, state, forceRedraw, source, isFinal); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isFinal) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Reapply, data); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Reapply, data); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -490,7 +490,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); // invoke the automation update based on what reset is. - StateUpdated.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); + StateFinalized.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From 0123fe1fbd448df9fd2a3873f3d8c761938e7427 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 17:52:06 +0100 Subject: [PATCH 023/212] Increment minor API version. --- Glamourer/Api/GlamourerApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 24ed840..62107a9 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 3; + public const int CurrentApiVersionMinor = 4; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); From cd6a6a462db26d97799b7da11d2d3c12c72f5bb5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 24 Jan 2025 16:54:22 +0000 Subject: [PATCH 024/212] [CI] Updating repo.json for testing_1.3.5.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 5f4ae31..076063b 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.3", + "TestingAssemblyVersion": "1.3.5.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 356b814102b65e961a8f9c5c9a2e536df4f7f16a Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:14:50 -0800 Subject: [PATCH 025/212] Append conversion for SetMetaFlag -> MetaIndex for help in moving down the API call to the state editor call. --- Glamourer/Designs/MetaIndex.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index edbf7b6..9cf6472 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -1,4 +1,5 @@ -using Glamourer.State; +using Glamourer.Api.Enums; +using Glamourer.State; namespace Glamourer.Designs; @@ -37,6 +38,16 @@ public static class MetaExtensions _ => (MetaFlag)byte.MaxValue, }; + public static MetaIndex ToIndex(this SetMetaFlag index) + => index switch + { + SetMetaFlag.Wetness => MetaIndex.Wetness, + SetMetaFlag.HatState => MetaIndex.HatState, + SetMetaFlag.VisorState => MetaIndex.VisorState, + SetMetaFlag.WeaponState => MetaIndex.WeaponState, + _ => (MetaIndex)byte.MaxValue, + }; + public static MetaIndex ToIndex(this MetaFlag index) => index switch { From 091aadd4a62c81f5569746a2f6ed7b50a3d7c512 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:25:40 -0800 Subject: [PATCH 026/212] Add Yield return Indices List for setter helper --- Glamourer/Designs/MetaIndex.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 9cf6472..9552e5a 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -48,6 +48,19 @@ public static class MetaExtensions _ => (MetaIndex)byte.MaxValue, }; + public static IEnumerable ToIndices(this SetMetaFlag index) + { + if (index.HasFlag(SetMetaFlag.Wetness)) + yield return MetaIndex.Wetness; + if (index.HasFlag(SetMetaFlag.HatState)) + yield return MetaIndex.HatState; + if (index.HasFlag(SetMetaFlag.VisorState)) + yield return MetaIndex.VisorState; + if (index.HasFlag(SetMetaFlag.WeaponState)) + yield return MetaIndex.WeaponState; + } + + public static MetaIndex ToIndex(this MetaFlag index) => index switch { From 439849edfac8cde614af660407cd229c0f1fd216 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:35:53 -0800 Subject: [PATCH 027/212] Append SetMetaState with object index. --- Glamourer/Api/ItemsApi.cs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index fd174ca..ef320f8 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -6,6 +6,7 @@ using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using static OtterGui.ItemSelector; namespace Glamourer.Api; @@ -96,9 +97,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager if (!ResolveBonusItem(slot, bonusItemId, out var item)) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); - var anyHuman = false; - var anyFound = false; + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + var anyHuman = false; + var anyFound = false; var anyUnlocked = false; foreach (var state in helpers.FindStates(playerName)) { @@ -125,6 +126,29 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc SetMetaState(int objectIndex, bool newValue, uint key, SetMetaFlag metaFlags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Value", newValue, "Key", key, "Flags", metaFlags); + if (metaFlags == 0) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + if (helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.ModelData.IsHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + // Grab MetaIndices from attached flags, and update the states. + var indices = metaFlags.ToIndices(); + foreach (var index in indices) + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + + return GlamourerApiEc.Success; } private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item) From 0ed4603ba526371986eec7aaa9bc79dbc43205ac Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:42:52 -0800 Subject: [PATCH 028/212] Corrected Paramater fields to include ApplyFlags and removed unessisary using. --- Glamourer/Api/ItemsApi.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index ef320f8..307a94f 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -6,7 +6,6 @@ using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using static OtterGui.ItemSelector; namespace Glamourer.Api; @@ -128,10 +127,10 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMetaState(int objectIndex, bool newValue, uint key, SetMetaFlag metaFlags) + public GlamourerApiEc SetMetaState(int objectIndex, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "Value", newValue, "Key", key, "Flags", metaFlags); - if (metaFlags == 0) + var args = ApiHelpers.Args("Index", objectIndex, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (metaStates == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); if (helpers.FindState(objectIndex) is not { } state) @@ -144,7 +143,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); // Grab MetaIndices from attached flags, and update the states. - var indices = metaFlags.ToIndices(); + var indices = metaStates.ToIndices(); foreach (var index in indices) stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); From 611200d311f1a0d7a9ae816a26f3a4fea886fa13 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:43:26 -0800 Subject: [PATCH 029/212] Added setMetaState by name --- Glamourer/Api/ItemsApi.cs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index 307a94f..0dce795 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -150,6 +150,44 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } + public GlamourerApiEc SetMetaStateName(string playerName, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", playerName, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (metaStates == 0) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); + + var indices = metaStates.ToIndices(); + var anyHuman = false; + var anyFound = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(playerName)) + { + anyFound = true; + if (!state.ModelData.IsHuman) + continue; + + anyHuman = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + // update all MetaStates for this ActorState + foreach (var index in indices) + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + } + + if (!anyFound) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!anyHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item) { var id = (CustomItemId)itemId; From 0f127a557d2e5dd4bb85b5e45e87bf1ba22b59a0 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:44:09 -0800 Subject: [PATCH 030/212] Introduced locks, if nessisary for change. --- Glamourer/Api/ItemsApi.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index 0dce795..c73c853 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -144,8 +144,11 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager // Grab MetaIndices from attached flags, and update the states. var indices = metaStates.ToIndices(); - foreach (var index in indices) - stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + foreach (var index in indices) + { + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + ApiHelpers.Lock(state, key, flags); + } return GlamourerApiEc.Success; } @@ -172,8 +175,11 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager anyUnlocked = true; // update all MetaStates for this ActorState - foreach (var index in indices) - stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + foreach (var index in indices) + { + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + ApiHelpers.Lock(state, key, flags); + } } if (!anyFound) From 5eb545f62d48ab5a653df95d97161edfa6234adc Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 13:09:49 -0800 Subject: [PATCH 031/212] Namechange: SetMetaState -> SetMeta Help clear up confusion on if it is in the StateApi section or the ItemsApi section (Which it is currently in) --- Glamourer/Api/ItemsApi.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index c73c853..b27264d 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -127,10 +127,10 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMetaState(int objectIndex, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMeta(int objectIndex, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); - if (metaStates == 0) + var args = ApiHelpers.Args("Index", objectIndex, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (types == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); if (helpers.FindState(objectIndex) is not { } state) @@ -143,7 +143,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); // Grab MetaIndices from attached flags, and update the states. - var indices = metaStates.ToIndices(); + var indices = types.ToIndices(); foreach (var index in indices) { stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); @@ -153,13 +153,13 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } - public GlamourerApiEc SetMetaStateName(string playerName, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaState(string playerName, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); - if (metaStates == 0) + var args = ApiHelpers.Args("Name", playerName, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (types == 0) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var indices = metaStates.ToIndices(); + var indices = types.ToIndices(); var anyHuman = false; var anyFound = false; var anyUnlocked = false; From f6c9ecad6076e32a379f245479b900291de29ad2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 23:28:23 +0100 Subject: [PATCH 032/212] Move MetaFlag completely to API, rename, cleanup. --- Glamourer.Api | 2 +- Glamourer/Api/ItemsApi.cs | 11 +++-- Glamourer/Automation/ApplicationType.cs | 3 +- Glamourer/Designs/ApplicationCollection.cs | 1 + Glamourer/Designs/ApplicationRules.cs | 3 +- Glamourer/Designs/DesignBase64Migration.cs | 3 +- Glamourer/Designs/Links/DesignMerger.cs | 3 +- Glamourer/Designs/MetaIndex.cs | 46 ++++++--------------- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + Glamourer/Gui/ToggleDrawData.cs | 3 +- Glamourer/State/StateIndex.cs | 3 +- 11 files changed, 33 insertions(+), 46 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 4ac38fb..8de6fa7 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 4ac38fbed6fb0f31c0b75de26950ab82d3bee258 +Subproject commit 8de6fa7246a403de50b3be4e17bb5f188717b279 diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index b27264d..ac971c9 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -127,9 +127,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMeta(int objectIndex, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaState(int objectIndex, MetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + var args = ApiHelpers.Args("Index", objectIndex, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); if (types == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); @@ -153,13 +153,12 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } - public GlamourerApiEc SetMetaState(string playerName, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaStateName(string playerName, MetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + var args = ApiHelpers.Args("Name", playerName, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); if (types == 0) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var indices = types.ToIndices(); var anyHuman = false; var anyFound = false; var anyUnlocked = false; @@ -175,7 +174,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager anyUnlocked = true; // update all MetaStates for this ActorState - foreach (var index in indices) + foreach (var index in types.ToIndices()) { stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); ApiHelpers.Lock(state, key, flags); diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 3d409cb..8871a0e 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.GameData; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 0fd18f0..13813a3 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -1,3 +1,4 @@ +using Glamourer.Api.Enums; using Glamourer.GameData; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 3c5fed2..703a3ae 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -1,4 +1,5 @@ -using Glamourer.GameData; +using Glamourer.Api.Enums; +using Glamourer.GameData; using Glamourer.State; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index a60c527..cab9e27 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,4 +1,5 @@ -using Glamourer.Services; +using Glamourer.Api.Enums; +using Glamourer.Services; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 0284322..847d5f1 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,4 +1,5 @@ -using Glamourer.Automation; +using Glamourer.Api.Enums; +using Glamourer.Automation; using Glamourer.Designs.Special; using Glamourer.GameData; using Glamourer.Interop.Material; diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 9552e5a..3fc4655 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -12,15 +12,6 @@ public enum MetaIndex ModelId = StateIndex.MetaModelId, } -[Flags] -public enum MetaFlag : byte -{ - Wetness = 0x01, - HatState = 0x02, - VisorState = 0x04, - WeaponState = 0x08, -} - public static class MetaExtensions { public static readonly IReadOnlyList AllRelevant = @@ -38,29 +29,6 @@ public static class MetaExtensions _ => (MetaFlag)byte.MaxValue, }; - public static MetaIndex ToIndex(this SetMetaFlag index) - => index switch - { - SetMetaFlag.Wetness => MetaIndex.Wetness, - SetMetaFlag.HatState => MetaIndex.HatState, - SetMetaFlag.VisorState => MetaIndex.VisorState, - SetMetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, - }; - - public static IEnumerable ToIndices(this SetMetaFlag index) - { - if (index.HasFlag(SetMetaFlag.Wetness)) - yield return MetaIndex.Wetness; - if (index.HasFlag(SetMetaFlag.HatState)) - yield return MetaIndex.HatState; - if (index.HasFlag(SetMetaFlag.VisorState)) - yield return MetaIndex.VisorState; - if (index.HasFlag(SetMetaFlag.WeaponState)) - yield return MetaIndex.WeaponState; - } - - public static MetaIndex ToIndex(this MetaFlag index) => index switch { @@ -68,9 +36,21 @@ public static class MetaExtensions MetaFlag.HatState => MetaIndex.HatState, MetaFlag.VisorState => MetaIndex.VisorState, MetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, + _ => (MetaIndex)byte.MaxValue, }; + public static IEnumerable ToIndices(this MetaFlag index) + { + if (index.HasFlag(MetaFlag.Wetness)) + yield return MetaIndex.Wetness; + if (index.HasFlag(MetaFlag.HatState)) + yield return MetaIndex.HatState; + if (index.HasFlag(MetaFlag.VisorState)) + yield return MetaIndex.VisorState; + if (index.HasFlag(MetaFlag.WeaponState)) + yield return MetaIndex.WeaponState; + } + public static string ToName(this MetaIndex index) => index switch { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 42eb8e9..dbe106b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; +using Glamourer.Api.Enums; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.History; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 75edc72..28afc2c 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.State; using Penumbra.GameData.Enums; diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index 0ac52ec..e3f1863 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.GameData; using Penumbra.GameData.Enums; From 630c4dd894b64c1c0da4af547b51dfda97ef0bbd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Jan 2025 14:00:36 +0100 Subject: [PATCH 033/212] Update Submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index fd38721..3c1260c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5 +Subproject commit 3c1260c9833303c2d33d12d6f77dc2b1afea3f34 diff --git a/Penumbra.GameData b/Penumbra.GameData index 5bac66e..bb59614 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 5bac66e5ad73e57919aff7f8b046606b75e191a2 +Subproject commit bb596141bb582505ae9fae1dd2914773503f4fe7 diff --git a/Penumbra.String b/Penumbra.String index 0647fbc..0bc2b0f 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 0647fbc5017ef9ced3f3ce1c2496eefd57c5b7a8 +Subproject commit 0bc2b0f66eee1a02c9575b2bb30f27ce166f8632 From b3afa2067cc1d7cf4c8a61688886d240e7f1a1e2 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 25 Jan 2025 13:02:30 +0000 Subject: [PATCH 034/212] [CI] Updating repo.json for testing_1.3.5.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 076063b..6ee99a9 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.4", + "TestingAssemblyVersion": "1.3.5.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2a067ef60b9b345b1903b4e246d48dfdea900138 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 13:36:06 +0100 Subject: [PATCH 035/212] Accept all existing facepaints. --- Glamourer/GameData/CustomizeSet.cs | 4 +- Glamourer/GameData/CustomizeSetFactory.cs | 1 - Glamourer/GameData/NpcCustomizeSet.cs | 45 ++++++++++++------- .../DebugTab/CustomizationServicePanel.cs | 34 ++++++++++++-- Penumbra.GameData | 2 +- 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index 178ef07..d0193aa 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -11,7 +11,7 @@ namespace Glamourer.GameData; /// public class CustomizeSet { - private NpcCustomizeSet _npcCustomizations; + private readonly NpcCustomizeSet _npcCustomizations; internal CustomizeSet(NpcCustomizeSet npcCustomizations, SubRace clan, Gender gender) { @@ -88,7 +88,7 @@ public class CustomizeSet { if (IsAvailable(index)) return DataByValue(index, value, out custom, face) >= 0 - || _npcCustomizations.CheckColor(index, value) + || _npcCustomizations.CheckValue(index, value) || NpcOptions.Any(t => t.Type == index && t.Value == value); custom = null; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 13a9865..77a6973 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -76,7 +76,6 @@ internal class CustomizeSetFactory( CustomizeIndex.Hairstyle, CustomizeIndex.LipColor, CustomizeIndex.SkinColor, - CustomizeIndex.FacePaint, CustomizeIndex.TailShape, }; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 4dbfd83..3cc19cd 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -3,6 +3,7 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Lumina.Excel.Sheets; using OtterGui.Services; +using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -40,17 +41,19 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList private readonly BitArray _eyeColors = new(256); private readonly BitArray _facepaintColors = new(256); private readonly BitArray _tattooColors = new(256); + private readonly BitArray _facepaints = new(128); - public bool CheckColor(CustomizeIndex type, CustomizeValue value) + public bool CheckValue(CustomizeIndex type, CustomizeValue value) => type switch { - CustomizeIndex.HairColor => _hairColors[value.Value], - CustomizeIndex.HighlightsColor => _hairColors[value.Value], - CustomizeIndex.EyeColorLeft => _eyeColors[value.Value], - CustomizeIndex.EyeColorRight => _eyeColors[value.Value], - CustomizeIndex.FacePaintColor => _facepaintColors[value.Value], - CustomizeIndex.TattooColor => _tattooColors[value.Value], - _ => false, + CustomizeIndex.HairColor => _hairColors[value.Value], + CustomizeIndex.HighlightsColor => _hairColors[value.Value], + CustomizeIndex.EyeColorLeft => _eyeColors[value.Value], + CustomizeIndex.EyeColorRight => _eyeColors[value.Value], + CustomizeIndex.FacePaintColor => _facepaintColors[value.Value], + CustomizeIndex.TattooColor => _tattooColors[value.Value], + CustomizeIndex.FacePaint when value.Value < 128 => _facepaints[value.Value], + _ => false, }; /// Create the data when ready. @@ -58,13 +61,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); Awaiter = waitTask.ContinueWith(_ => - { - var watch = Stopwatch.StartNew(); - var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); - var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); - FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); - Time = watch.ElapsedMilliseconds; - }); + { + var watch = Stopwatch.StartNew(); + var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); + var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); + FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); + Time = watch.ElapsedMilliseconds; + }) + .ContinueWith(_ => CheckFacepaintFiles(data, _facepaints)); } /// Create data from event NPCs. @@ -323,6 +327,17 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return (true, customize); } + /// Check decal files for existence. + private static void CheckFacepaintFiles(IDataManager data, BitArray facepaints) + { + for (var i = 0; i < 128; ++i) + { + var path = GamePaths.Character.Tex.DecalPath("face", (PrimaryId)i); + if (data.FileExists(path)) + facepaints[i] = true; + } + } + /// public IEnumerator GetEnumerator() => _data.GetEnumerator(); diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index afc7d56..565b6ba 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -28,9 +28,35 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr DrawNpcCustomizationInfo(set); } + DrawFacepaintInfo(); DrawColorInfo(); } + private void DrawFacepaintInfo() + { + using var tree = ImUtf8.TreeNode("NPC Facepaints"u8); + if (!tree) + return; + + using var table = ImUtf8.Table("data"u8, 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Id"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Facepaint"u8); + + for (var i = 0; i < 128; ++i) + { + var index = new CustomizeValue((byte)i); + ImUtf8.DrawTableColumn($"{i:D3}"); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaint, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + } + } private void DrawColorInfo() { using var tree = ImUtf8.TreeNode("NPC Colors"u8); @@ -57,16 +83,16 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr var index = new CustomizeValue((byte)i); ImUtf8.DrawTableColumn($"{i:D3}"); using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.HairColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.HairColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.EyeColorLeft, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.EyeColorLeft, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.FacePaintColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaintColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.TattooColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.TattooColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); } diff --git a/Penumbra.GameData b/Penumbra.GameData index bb59614..4880433 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bb596141bb582505ae9fae1dd2914773503f4fe7 +Subproject commit 488043381efd42238e9c1328ccab3b80abd81ea7 From 1f255a98e6aef912680f2d247133bdecccfea028 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 13:55:28 +0100 Subject: [PATCH 036/212] Fix issue with scaling after zone change. --- Glamourer/Glamourer.cs | 9 ----- Glamourer/Interop/ScalingService.cs | 54 +++++++++++++++++++++++------ Penumbra.GameData | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9c4583f..cf0b278 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -47,15 +47,6 @@ public class Glamourer : IDalamudPlugin _services.GetService(); // initialize commands. _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); - - //var text = File.ReadAllBytes(@"C:\FFXIVMods\PBDTest\files\human.pbd"); - //var pbd = new PbdFile(text); - //var roundtrip = pbd.Write(); - //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\Vanilla resaved save.pbd", roundtrip); - //var deformer = pbd.Deformers.FirstOrDefault(d => d.GenderRace is GenderRace.RoegadynFemale)!.RacialDeformer; - //deformer.DeformMatrices["ya_fukubu_phys"] = deformer.DeformMatrices["j_kosi"]; - //var aleks = pbd.Write(); - //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\rue.pbd", aleks); } catch { diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 141d5f2..68dc2e8 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -1,19 +1,27 @@ -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Penumbra.GameData; using Penumbra.GameData.Interop; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.State; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; +using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; namespace Glamourer.Interop; public unsafe class ScalingService : IDisposable { - public ScalingService(IGameInteropProvider interop) + private readonly ActorManager _actors; + private readonly StateManager _state; + + public ScalingService(IGameInteropProvider interop, StateManager state, ActorManager actors) { + _state = state; + _actors = actors; interop.InitializeFromAttributes(this); _setupMountHook = interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); @@ -79,7 +87,16 @@ public unsafe class ScalingService : IDisposable var mdl = owner.Model; var oldRace = owner.AsCharacter->DrawData.CustomizeData.Race; if (mdl.IsHuman) + { owner.AsCharacter->DrawData.CustomizeData.Race = mdl.AsHuman->Customize.Race; + } + else + { + var actor = _actors.FromObject(owner, out _, true, false, true); + if (_state.TryGetValue(actor, out var state)) + owner.AsCharacter->DrawData.CustomizeData.Race = (byte)state.ModelData.Customize.Race; + } + _placeMinionHook.Original(companion); owner.AsCharacter->DrawData.CustomizeData.Race = oldRace; } @@ -103,12 +120,20 @@ public unsafe class ScalingService : IDisposable character->DrawData.CustomizeData.Tribe, character->DrawData.CustomizeData[(int)CustomizeIndex.Height]); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void SetScaleCustomize(Character* character, Model model) + private void SetScaleCustomize(Character* character, Model model) { - if (!model.IsHuman) + if (model.IsHuman) + { + SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Tribe, model.AsHuman->Customize.Sex); + return; + } + + var actor = _actors.FromObject(character, out _, true, false, true); + if (!_state.TryGetValue(actor, out var state)) return; - SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Tribe, model.AsHuman->Customize.Sex); + ref var customize = ref state.ModelData.Customize; + SetScaleCustomize(character, (byte)customize.Race, (byte)customize.Clan, customize.Gender.ToGameByte()); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -120,13 +145,22 @@ public unsafe class ScalingService : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void SetHeightCustomize(Character* character, Model model) + private void SetHeightCustomize(Character* character, Model model) { - if (!model.IsHuman) + if (model.IsHuman) + { + SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Tribe, + model.AsHuman->Customize[(int)CustomizeIndex.Height]); + return; + } + + var actor = _actors.FromObject(character, out _, true, false, true); + if (!_state.TryGetValue(actor, out var state)) return; - SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Tribe, - model.AsHuman->Customize[(int)CustomizeIndex.Height]); + ref var customize = ref state.ModelData.Customize; + SetHeightCustomize(character, customize.Gender.ToGameByte(), customize.BodyType.Value, (byte)customize.Clan, + customize[global::Penumbra.GameData.Enums.CustomizeIndex.Height].Value); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Penumbra.GameData b/Penumbra.GameData index 4880433..33fea10 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 488043381efd42238e9c1328ccab3b80abd81ea7 +Subproject commit 33fea10e18ec9f8a5b309890de557fcb25780086 From 8e0908dbf7fcfe4cd1ee87cb0bba7408cd63887a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 22:39:57 +0100 Subject: [PATCH 037/212] Add more settings to multi design panel. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 139 +++++++++++++++++- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index a1f17d4..3d10e64 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -25,6 +25,10 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); DrawMultiQuickDesignBar(offset); + DrawMultiLock(offset); + DrawMultiResetSettings(offset); + DrawMultiResetDyes(offset); + DrawMultiForceRedraw(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -43,19 +47,46 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.SetCursorPos(startPos); } + private void ResetCounts() + { + _numQuickDesignEnabled = 0; + _numDesignsLocked = 0; + _numDesignsForcedRedraw = 0; + _numDesignsResetSettings = 0; + _numDesignsResetDyes = 0; + } + + private bool CountLeaves(DesignFileSystem.IPath path) + { + if (path is not DesignFileSystem.Leaf l) + return false; + + if (l.Value.QuickDesign) + ++_numQuickDesignEnabled; + if (l.Value.WriteProtected()) + ++_numDesignsLocked; + if (l.Value.ResetTemporarySettings) + ++_numDesignsResetSettings; + if (l.Value.ForcedRedraw) + ++_numDesignsForcedRedraw; + if (l.Value.ResetAdvancedDyes) + ++_numDesignsResetDyes; + return true; + } + private int DrawDesignList() { + ResetCounts(); using var tree = ImUtf8.TreeNode("Currently Selected Objects"u8, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGui.Separator(); if (!tree) - return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); + return selector.SelectedPaths.Count(CountLeaves); var sizeType = new Vector2(ImGui.GetFrameHeight()); var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType.X - 4 * ImGui.GetStyle().CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; - _numQuickDesignEnabled = 0; var numDesigns = 0; using (var table = ImUtf8.Table("mods"u8, 3, ImGuiTableFlags.RowBg)) { @@ -81,12 +112,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImUtf8.DrawFrameColumn(text); ImUtf8.DrawFrameColumn(fullName); - if (path is not DesignFileSystem.Leaf l2) - continue; - - ++numDesigns; - if (l2.Value.QuickDesign) - ++_numQuickDesignEnabled; + if (CountLeaves(path)) + ++numDesigns; } } @@ -96,6 +123,10 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private string _tag = string.Empty; private int _numQuickDesignEnabled; + private int _numDesignsLocked; + private int _numDesignsForcedRedraw; + private int _numDesignsResetSettings; + private int _numDesignsResetDyes; private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; @@ -161,6 +192,98 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } + private void DrawMultiLock(float offset) + { + ImUtf8.TextFrameAligned("Multi Lock:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsLocked; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs are already write protected." + : $"Write-protect all {_numDesigns} designs. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Turn Write-Protected"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetWriteProtection(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs are write-protected." + : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs."; + if (ImUtf8.ButtonEx("Remove Write-Protection"u8, tt, buttonWidth, _numDesignsLocked == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetWriteProtection(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiResetSettings(float offset) + { + ImUtf8.TextFrameAligned("Settings:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsResetSettings; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already reset temporary settings." + : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Set Reset Settings"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetTemporarySettings(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsResetSettings == 0 + ? $"None of the {_numDesigns} selected designs reset temporary settings." + : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; + if (ImUtf8.ButtonEx("Remove Reset Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetTemporarySettings(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiResetDyes(float offset) + { + ImUtf8.TextFrameAligned("Adv. Dyes:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsResetDyes; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already reset advanced dyes." + : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Set Reset Dyes"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetAdvancedDyes(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs reset advanced dyes." + : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs."; + if (ImUtf8.ButtonEx("Remove Reset Dyes"u8, tt, buttonWidth, _numDesignsResetDyes == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetAdvancedDyes(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiForceRedraw(float offset) + { + ImUtf8.TextFrameAligned("Redrawing:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsForcedRedraw; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already force redraws." + : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Force Redraws"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeForcedRedraw(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs force redraws." + : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs."; + if (ImUtf8.ButtonEx("Remove Forced Redraws"u8, tt, buttonWidth, _numDesignsForcedRedraw == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeForcedRedraw(design.Value, false); + ImGui.Separator(); + } + private void DrawMultiColor(Vector2 width, float offset) { ImUtf8.TextFrameAligned("Multi Colors:"); From 9a1cf3d9e6023de497d7fa2c29add8490ab539ee Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 22:45:59 +0100 Subject: [PATCH 038/212] Better --- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 3d10e64..9e894c4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -224,7 +224,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e var tt = diff == 0 ? $"All {_numDesigns} selected designs already reset temporary settings." : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Settings"u8, tt, buttonWidth, diff == 0)) + if (ImUtf8.ButtonEx("Set Reset Temp. Settings"u8, tt, buttonWidth, diff == 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, true); @@ -232,7 +232,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e tt = _numDesignsResetSettings == 0 ? $"None of the {_numDesigns} selected designs reset temporary settings." : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; - if (ImUtf8.ButtonEx("Remove Reset Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) + if (ImUtf8.ButtonEx("Remove Reset Temp. Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, false); ImGui.Separator(); From da46705b52a4537621cc728dcf56bb7525fa950b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 Jan 2025 16:06:50 +0100 Subject: [PATCH 039/212] Use new settings API. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 76 ++++++++++++++----- Penumbra.Api | 2 +- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 13be628..27446ea 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -37,6 +37,7 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersion = 3; public const int RequiredPenumbraFeatureVersionTemp = 4; public const int RequiredPenumbraFeatureVersionTemp2 = 5; + public const int RequiredPenumbraFeatureVersionTemp3 = 6; private const int Key = -1610; @@ -56,7 +57,9 @@ public class PenumbraService : IDisposable 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; @@ -132,22 +135,28 @@ public class PenumbraService : IDisposable public ModSettings GetModSettings(in Mod mod, out string source) { - source = string.Empty; + source = string.Empty; if (!Available) return ModSettings.Empty; try { var collection = _currentCollection!.Invoke(ApiCollectionType.Current); - if (_queryTemporaryModSettings != null) - { - var tempEc = _queryTemporaryModSettings.Invoke(collection!.Value.Id, mod.DirectoryName, out var tempTuple, out source); - if (tempEc is PenumbraApiEc.Success && tempTuple != null) - return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, - tempTuple.Value.ForceInherit, false); - } + return GetSettings(collection!.Value.Id, mod.DirectoryName, mod.Name, out source); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}"); + return ModSettings.Empty; + } + } - var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); + private ModSettings GetSettings(Guid collection, string modDirectory, string modName, out string source) + { + if (_getCurrentSettingsWithTemp != null) + { + source = string.Empty; + var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, Key); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -155,11 +164,23 @@ public class PenumbraService : IDisposable ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty; } - catch (Exception ex) + + if (_queryTemporaryModSettings != null) { - Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}"); - return ModSettings.Empty; + var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source); + if (tempEc is PenumbraApiEc.Success && tempTuple != null) + return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, + tempTuple.Value.ForceInherit, false); } + + source = string.Empty; + var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory); + if (ec2 is not PenumbraApiEc.Success) + return ModSettings.Empty; + + return tuple2.HasValue + ? new ModSettings(tuple2.Value.Item3, tuple2.Value.Item2, tuple2.Value.Item1, false, false) + : ModSettings.Empty; } public (Guid Id, string Name)? CollectionByIdentifier(string identifier) @@ -183,13 +204,23 @@ public class PenumbraService : IDisposable { var allMods = _getMods!.Invoke(); var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + if (_getAllSettings != null) + { + 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), + 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(); + } + return allMods - .Select(m => (m.Key, m.Value, _getCurrentSettings!.Invoke(collection!.Value.Id, m.Key))) - .Where(t => t.Item3.Item1 is PenumbraApiEc.Success) - .Select(t => (new Mod(t.Item2, t.Item1), - !t.Item3.Item2.HasValue - ? ModSettings.Empty - : new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1, false, false))) + .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) @@ -455,7 +486,14 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + { + _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); + _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + } + } } Available = true; @@ -488,6 +526,8 @@ public class PenumbraService : IDisposable _getMods = null; _currentCollection = null; _getCurrentSettings = null; + _getCurrentSettingsWithTemp = null; + _getAllSettings = null; _inheritMod = null; _setMod = null; _setModPriority = null; diff --git a/Penumbra.Api b/Penumbra.Api index b4e716f..35b25be 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit b4e716f86d94cd4d98d8f58e580ed5f619ea87ae +Subproject commit 35b25bef92e9b0be96c44c150a3df89d848d2658 From 87016419c5cc5c33bb2ef03d3b553d0397984b96 Mon Sep 17 00:00:00 2001 From: Diorik Date: Tue, 4 Feb 2025 00:46:12 -0600 Subject: [PATCH 040/212] Add "Reset Design" command A new command to reapply automation while resetting the random design regardless of settings. --- Glamourer/Api/StateApi.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Gui/DesignQuickBar.cs | 4 ++-- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 ++-- Glamourer/Services/CommandService.cs | 11 +++++++---- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index eaf9d01..b2fdc9b 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -301,7 +301,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void RevertToAutomation(Actor actor, ActorState state, uint key, ApplyFlag flags) { var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; - _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, out var forcedRedraw); + _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source); ApiHelpers.Lock(state, key, flags); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 1655c15..7f75674 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -225,7 +225,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } - public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, out bool forcedRedraw) + public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, bool forcedNew, out bool forcedRedraw) { forcedRedraw = false; if (!_config.EnableAutoDesigns) @@ -235,7 +235,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ResetState(state, StateSource.Game); if (GetPlayerSet(identifier, out var set)) - Reduce(actor, state, set, false, false, false, out forcedRedraw); + Reduce(actor, state, set, false, false, forcedNew, out forcedRedraw); } public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 50f86fd..b58643c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -251,7 +251,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual); } } @@ -291,7 +291,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index d8f3cd1..9c8f3cf 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -393,7 +393,7 @@ public class ActorPanel "Reapply the current automation state for the character on top of its current state..", !_config.EnableAutoDesigns || _state!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual); } @@ -402,7 +402,7 @@ public class ActorPanel "Try to revert the character to the state it would have using automated designs.", !_config.EnableAutoDesigns || _state!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 98dfa19..bffc072 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -121,8 +121,9 @@ public class CommandService : IDisposable, IApiService "apply" => Apply(argument), "reapply" => ReapplyState(argument), "revert" => Revert(argument), - "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), - "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), + "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false, false), + "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), + "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), @@ -151,6 +152,8 @@ public class CommandService : IDisposable, IApiService "Reapplies the current automation state on top of the characters current state.. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("reverttoautomation", "Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder().AddCommand("resetdesign", + "Reapplies the current automation and resets the random design. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() @@ -306,7 +309,7 @@ public class CommandService : IDisposable, IApiService return true; } - private bool ReapplyAutomation(string argument, string command, bool revert) + private bool ReapplyAutomation(string argument, string command, bool revert, bool forcedNew) { if (argument.Length == 0) { @@ -328,7 +331,7 @@ public class CommandService : IDisposable, IApiService { if (_stateManager.GetOrCreate(identifier, actor, out var state)) { - _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, forcedNew, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, revert, StateSource.Manual); } } From cf308fc118fbcc3fd83c7ad8bb10d71bb712068d Mon Sep 17 00:00:00 2001 From: Diorik Date: Tue, 4 Feb 2025 01:54:34 -0600 Subject: [PATCH 041/212] Prevent repeating random design Cache the last selected random design and prevent it from being chosen again. --- Glamourer/Designs/Special/RandomDesignGenerator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 7ed4452..1bee8ad 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -5,15 +5,20 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService { private readonly Random _rng = new(); + private Design? _lastDesign = null; public Design? Design(IReadOnlyList localDesigns) { if (localDesigns.Count == 0) return null; + if (_lastDesign != null && localDesigns.Count > 1) + localDesigns = localDesigns.Where(d => d != _lastDesign).ToList(); + var idx = _rng.Next(0, localDesigns.Count); Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - return localDesigns[idx]; + _lastDesign = localDesigns[idx]; + return _lastDesign; } public Design? Design() From 5d1c65cce7ef82acdace9743a288ce3d6d44594d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 16:27:10 +0100 Subject: [PATCH 042/212] Fix overwriting with current state keeping old advanced dyes. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index dbe106b..dd198ae 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -636,6 +636,7 @@ public class DesignPanel var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) ?? throw new Exception("The clipboard did not contain valid data."); + panel._selector.Selected!.GetMaterialDataRef().Clear(); panel._manager.ApplyDesign(panel._selector.Selected!, design); } catch (Exception ex) From d849506ecd1d9e6c76dde4c145f3eca03ee3c10d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 16:52:59 +0100 Subject: [PATCH 043/212] 1.3.6.0 --- Glamourer/Gui/GlamourerChangelog.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 0c9d99b..4c8b365 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -39,6 +39,7 @@ public class GlamourerChangelog Add1_3_3_0(Changelog); Add1_3_4_0(Changelog); Add1_3_5_0(Changelog); + Add1_3_6_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -59,6 +60,22 @@ public class GlamourerChangelog } } + private static void Add1_3_6_0(Changelog log) + => log.NextVersion("Version 1.3.6.0") + .RegisterHighlight("Added some new multi design selection functionality to change design settings of many designs at once.") + .RegisterEntry("Also added the number of selected designs and folders to the multi design selection display.", 1) + .RegisterEntry("Glamourer will now use temporary settings when saving mod associations, if they exist in Penumbra.") + .RegisterEntry("Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") + .RegisterEntry("Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") + .RegisterEntry("Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") + .RegisterEntry("All existing facepaints should now be accepted in designs, including NPC facepaints.") + .RegisterEntry("Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") + .RegisterEntry("Fixed an issue with racial mount and accessory scaling when changing zones on a changed race.") + .RegisterEntry("Fixed issues with the detection of gear set changes in certain circumstances (Thanks Cordelia).") + .RegisterEntry("Fixed an issue with the Force to Inherit checkbox in mod associations.") + .RegisterEntry("Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") + .RegisterEntry("Added new IPC to set a meta flag on actors. (for/from Cordelia)."); + private static void Add1_3_5_0(Changelog log) => log.NextVersion("Version 1.3.5.0") .RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") From 2c7b7d59be3dab950eeca4d1684828bfea3dcd2a Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 6 Feb 2025 15:55:34 +0000 Subject: [PATCH 044/212] [CI] Updating repo.json for 1.3.6.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 6ee99a9..d506898 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.5", + "AssemblyVersion": "1.3.6.0", + "TestingAssemblyVersion": "1.3.6.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 1df2a46884340c1710ff2e08d38d46e8d4047c6f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 17:00:25 +0100 Subject: [PATCH 045/212] Update Submodule Versions. --- Glamourer.Api | 2 +- Penumbra.Api | 2 +- Penumbra.String | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 8de6fa7..9f9bdf0 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 8de6fa7246a403de50b3be4e17bb5f188717b279 +Subproject commit 9f9bdf0873899d2e45fabaca446bb1624303b418 diff --git a/Penumbra.Api b/Penumbra.Api index 35b25be..c678090 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 35b25bef92e9b0be96c44c150a3df89d848d2658 +Subproject commit c67809057fac73a0fd407e3ad567f0aa6bc0bc37 diff --git a/Penumbra.String b/Penumbra.String index 0bc2b0f..4eb7c11 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 0bc2b0f66eee1a02c9575b2bb30f27ce166f8632 +Subproject commit 4eb7c118cdac5873afb97cb04719602f061f03b7 From 67fd65d366133ba0569ce9a3a70760e8f43fec5a Mon Sep 17 00:00:00 2001 From: Diorik Date: Thu, 6 Feb 2025 12:49:23 -0600 Subject: [PATCH 046/212] Make PreventRandomc figurable, clean up logic Will no longer hold design reference or make redundant copy of list --- Glamourer/Configuration.cs | 1 + .../Designs/Special/RandomDesignGenerator.cs | 17 +++++++++-------- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4b59191..5f838a8 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -67,6 +67,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool UseTemporarySettings { get; set; } = true; public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + public bool PreventRandomRepeats { get; set; } = false; public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 1bee8ad..3ff353b 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -1,24 +1,25 @@ -using OtterGui.Services; +using OtterGui; +using OtterGui.Services; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { private readonly Random _rng = new(); - private Design? _lastDesign = null; + private Guid? _lastDesignID = null; - public Design? Design(IReadOnlyList localDesigns) + public Design? Design(IList localDesigns) { if (localDesigns.Count == 0) return null; - if (_lastDesign != null && localDesigns.Count > 1) - localDesigns = localDesigns.Where(d => d != _lastDesign).ToList(); + if (config.PreventRandomRepeats && _lastDesignID != null && localDesigns.Count > 1 && localDesigns.FindFirst(d => d.Identifier == _lastDesignID, out var found)) + localDesigns.Remove(found); var idx = _rng.Next(0, localDesigns.Count); Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesign = localDesigns[idx]; - return _lastDesign; + _lastDesignID = localDesigns[idx].Identifier; + return localDesigns[idx]; } public Design? Design() diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index ab40a48..2b9548f 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -109,6 +109,9 @@ public class SettingsTab( Checkbox("Use Temporary Mod Settings", "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings, v => config.UseTemporarySettings = v); + Checkbox("Prevent Random Design Repeats", + "When using random designs, prevent the same design from being chosen twice in a row.", + config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); ImGui.NewLine(); } From 5ca151b675e3f9484dac8ff06236a48f045895db Mon Sep 17 00:00:00 2001 From: Diorik Date: Thu, 6 Feb 2025 13:29:47 -0600 Subject: [PATCH 047/212] PreventRandom use WeakReference, reroll rand instead of changing list --- .../Designs/Special/RandomDesignGenerator.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 3ff353b..b34a8ba 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -6,19 +6,20 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { private readonly Random _rng = new(); - private Guid? _lastDesignID = null; + private WeakReference _lastDesign = new(null, false); - public Design? Design(IList localDesigns) + public Design? Design(IReadOnlyList localDesigns) { if (localDesigns.Count == 0) return null; - if (config.PreventRandomRepeats && _lastDesignID != null && localDesigns.Count > 1 && localDesigns.FindFirst(d => d.Identifier == _lastDesignID, out var found)) - localDesigns.Remove(found); - - var idx = _rng.Next(0, localDesigns.Count); + int idx; + do + idx = _rng.Next(0, localDesigns.Count); + while (config.PreventRandomRepeats && localDesigns.Count > 1 && _lastDesign.TryGetTarget(out var lastDesign) && lastDesign == localDesigns[idx]); + Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesignID = localDesigns[idx].Identifier; + _lastDesign.SetTarget(localDesigns[idx]); return localDesigns[idx]; } From 4748d1e211731abf01ce3ca5b369065d854a2b67 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:30:24 +0100 Subject: [PATCH 048/212] Allow filtered item names. --- Glamourer/Designs/DesignBase.cs | 3 ++ Glamourer/Designs/DesignData.cs | 66 ++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 30d4ddd..b21c433 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -195,6 +195,9 @@ public class DesignBase return true; } + public IEnumerable FilteredItemNames + => _designData.FilteredItemNames(Application.Equip, Application.BonusItem); + internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions) => new(this, restrictions); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4205996..bba0ccb 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -46,20 +46,9 @@ public unsafe struct DesignData public DesignData() { } + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] public readonly bool ContainsName(LowerString name) - => name.IsContained(_nameHead) - || name.IsContained(_nameBody) - || name.IsContained(_nameHands) - || name.IsContained(_nameLegs) - || name.IsContained(_nameFeet) - || name.IsContained(_nameEars) - || name.IsContained(_nameNeck) - || name.IsContained(_nameWrists) - || name.IsContained(_nameRFinger) - || name.IsContained(_nameLFinger) - || name.IsContained(_nameMainhand) - || name.IsContained(_nameOffhand) - || name.IsContained(_nameGlasses); + => ItemNames.Any(name.IsContained); public readonly StainIds Stain(EquipSlot slot) { @@ -76,6 +65,57 @@ public unsafe struct DesignData public readonly bool Crest(CrestFlag slot) => CrestVisibility.HasFlag(slot); + public readonly IEnumerable ItemNames + { + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + get + { + yield return _nameHead; + yield return _nameBody; + yield return _nameHands; + yield return _nameLegs; + yield return _nameFeet; + yield return _nameEars; + yield return _nameNeck; + yield return _nameWrists; + yield return _nameRFinger; + yield return _nameLFinger; + yield return _nameMainhand; + yield return _nameOffhand; + yield return _nameGlasses; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public readonly IEnumerable FilteredItemNames(EquipFlag item, BonusItemFlag bonusItem) + { + if (item.HasFlag(EquipFlag.Head)) + yield return _nameHead; + if (item.HasFlag(EquipFlag.Body)) + yield return _nameBody; + if (item.HasFlag(EquipFlag.Hands)) + yield return _nameHands; + if (item.HasFlag(EquipFlag.Legs)) + yield return _nameLegs; + if (item.HasFlag(EquipFlag.Feet)) + yield return _nameFeet; + if (item.HasFlag(EquipFlag.Ears)) + yield return _nameEars; + if (item.HasFlag(EquipFlag.Neck)) + yield return _nameNeck; + if (item.HasFlag(EquipFlag.Wrist)) + yield return _nameWrists; + if (item.HasFlag(EquipFlag.RFinger)) + yield return _nameRFinger; + if (item.HasFlag(EquipFlag.LFinger)) + yield return _nameLFinger; + if (item.HasFlag(EquipFlag.Mainhand)) + yield return _nameMainhand; + if (item.HasFlag(EquipFlag.Offhand)) + yield return _nameOffhand; + if (bonusItem.HasFlag(BonusItemFlag.Glasses)) + yield return _nameGlasses; + } public readonly FullEquipType MainhandType => _typeMainhand; From ab2a3f5bd9790e29d653bb20d4ae4b9a4208cb08 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:30:35 +0100 Subject: [PATCH 049/212] Add new colors. --- Glamourer/Gui/Colors.cs | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index b7f9737..e19639c 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -29,6 +29,9 @@ public enum ColorId TriStateNeutral, BattleNpc, EventNpc, + ModdedItemMarker, + ContainsItemsEnabled, + ContainsItemsDisabled, } public static class Colors @@ -39,32 +42,35 @@ public static class Colors => color switch { // @formatter:off - ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), - ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), - ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), - ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), - ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), - ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), - ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), - ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), - ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), - ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), - ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), - ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), - ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), - ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), - ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), - ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), - ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), - ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), - ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), - ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), - ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), - ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), - ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), - ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), - ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), - _ => (0x00000000, string.Empty, string.Empty ), + ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), + ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), + ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), + ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), + ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), + ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), + ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), + ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), + ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), + ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), + ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), + ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), + ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), + ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), + ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), + ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), + ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), + ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), + ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), + ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), + ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), + ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), + ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), + ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.ModdedItemMarker => (0xFFFF20FF, "Modded Item Marker", "The color of dot in the unlocks overview tab signaling that the item is modded in the currently selected Penumbra collection." ), + ColorId.ContainsItemsEnabled => (0xFFA0F0A0, "Enabled Mod Contains Design Items", "The color of enabled mods in the associated mod dropdown menu when they contain items used in this design." ), + ColorId.ContainsItemsDisabled => (0x80A0F0A0, "Disabled Mod Contains Design Items", "The color of disabled mods in the associated mod dropdown menu when they contain items used in this design." ), + _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; From d56c2db547e64496e794e68ee1771c49096b0f58 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:32:23 +0100 Subject: [PATCH 050/212] 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 From f7d6e75a9b9e3687958c590cb90f8a3993ff62b8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 13 Feb 2025 15:43:08 +0000 Subject: [PATCH 051/212] [CI] Updating repo.json for testing_1.3.6.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index d506898..76009f4 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.0", + "TestingAssemblyVersion": "1.3.6.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 11684609427ecfc84dedc4c4d826a3e3bae39148 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 17 Feb 2025 17:40:49 +0100 Subject: [PATCH 052/212] Add button to reset glamourer temporary settings to qdb. --- Glamourer/Gui/DesignQuickBar.cs | 43 +++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 238 +++++++++--------- Glamourer/State/StateEditor.cs | 3 +- OtterGui | 2 +- 4 files changed, 167 insertions(+), 119 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b58643c..1e2904c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -7,11 +7,13 @@ using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Text; using Penumbra.GameData.Actors; namespace Glamourer.Gui; @@ -26,6 +28,7 @@ public enum QdbButtons RevertEquip = 0x10, RevertCustomize = 0x20, ReapplyAutomation = 0x40, + ResetSettings = 0x80, } public sealed class DesignQuickBar : Window, IDisposable @@ -40,6 +43,7 @@ public sealed class DesignQuickBar : Window, IDisposable private readonly StateManager _stateManager; private readonly AutoDesignApplier _autoDesignApplier; private readonly ObjectManager _objects; + private readonly PenumbraService _penumbra; private readonly IKeyState _keyState; private readonly ImRaii.Style _windowPadding = new(); private readonly ImRaii.Color _windowColor = new(); @@ -47,7 +51,7 @@ public sealed class DesignQuickBar : Window, IDisposable private int _numButtons; public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, - ObjectManager objects, AutoDesignApplier autoDesignApplier) + ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; @@ -56,6 +60,7 @@ public sealed class DesignQuickBar : Window, IDisposable _keyState = keyState; _objects = objects; _autoDesignApplier = autoDesignApplier; + _penumbra = penumbra; IsOpen = _config.Ephemeral.ShowDesignQuickBar; DisableWindowSounds = true; Size = Vector2.Zero; @@ -122,6 +127,7 @@ public sealed class DesignQuickBar : Window, IDisposable DrawRevertAdvancedCustomization(buttonSize); DrawRevertAutomationButton(buttonSize); DrawReapplyAutomationButton(buttonSize); + DrawResetSettingsButton(buttonSize); } private ActorIdentifier _playerIdentifier; @@ -392,10 +398,41 @@ public sealed class DesignQuickBar : Window, IDisposable _stateManager.ResetEquip(state!, StateSource.Manual); } + private void DrawResetSettingsButton(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerData.Valid) + { + available |= 1; + tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_playerIdentifier}."; + } + + if (_targetIdentifier.IsValid && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_targetIdentifier}."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available to identify their collections."; + + var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); + ImGui.SameLine(); + if (clicked) + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, int available) { - ImGuiUtil.DrawDisabledButton(icon.ToIconString(), buttonSize, tooltip, available == 0, true); + ImUtf8.IconButton(icon, tooltip, buttonSize, available == 0); if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) return (true, _playerIdentifier, _playerData, _playerState); if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) @@ -441,6 +478,8 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) ++_numButtons; + if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) + ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) { ++_numButtons; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index ab40a48..4ee261b 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -9,7 +9,6 @@ using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; using ImGuiNET; -using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; @@ -39,12 +38,12 @@ public class SettingsTab( public void DrawContent() { - using var child = ImRaii.Child("MainWindowChild"); + using var child = ImUtf8.Child("MainWindowChild"u8, default); if (!child) return; - Checkbox("Enable Auto Designs", - "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", + Checkbox("Enable Auto Designs"u8, + "Enable the application of designs associated to characters in the Automation tab to be applied automatically."u8, config.EnableAutoDesigns, v => { config.EnableAutoDesigns = v; @@ -55,7 +54,7 @@ public class SettingsTab( ImGui.NewLine(); ImGui.NewLine(); - using (ImRaii.Child("SettingsChild")) + using (ImUtf8.Child("SettingsChild"u8, default)) { DrawBehaviorSettings(); DrawDesignDefaultSettings(); @@ -70,44 +69,44 @@ public class SettingsTab( private void DrawBehaviorSettings() { - if (!ImGui.CollapsingHeader("Glamourer Behavior")) + if (!ImUtf8.CollapsingHeader("Glamourer Behavior"u8)) return; - Checkbox("Always Apply Entire Weapon for Mainhand", - "When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.", + Checkbox("Always Apply Entire Weapon for Mainhand"u8, + "When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons."u8, config.ChangeEntireItem, v => config.ChangeEntireItem = v); - Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender", - "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.", + Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender"u8, + "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race."u8, config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v); - Checkbox("Do Not Apply Unobtained Items in Automation", - "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not.", + Checkbox("Do Not Apply Unobtained Items in Automation"u8, + "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not."u8, config.UnlockedItemMode, v => config.UnlockedItemMode = v); - Checkbox("Respect Manual Changes When Editing Automation", - "Whether changing any currently active automation group will respect manual changes to the character before re-applying the changed automation or not.", + Checkbox("Respect Manual Changes When Editing Automation"u8, + "Whether changing any currently active automation group will respect manual changes to the character before re-applying the changed automation or not."u8, config.RespectManualOnAutomationUpdate, v => config.RespectManualOnAutomationUpdate = v); - Checkbox("Enable Festival Easter-Eggs", - "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this.", + Checkbox("Enable Festival Easter-Eggs"u8, + "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this."u8, config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); - Checkbox("Auto-Reload Gear", - "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.", + Checkbox("Auto-Reload Gear"u8, + "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); - Checkbox("Revert Manual Changes on Zone Change", - "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", + Checkbox("Revert Manual Changes on Zone Change"u8, + "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options", - "Enable the display and editing of advanced customization options like arbitrary colors.", + Checkbox("Enable Advanced Customization Options"u8, + "Enable the display and editing of advanced customization options like arbitrary colors."u8, config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); - Checkbox("Enable Advanced Dye Options", - "Enable the display and editing of advanced dyes (color sets) for all equipment", + Checkbox("Enable Advanced Dye Options"u8, + "Enable the display and editing of advanced dyes (color sets) for all equipment"u8, config.UseAdvancedDyes, v => config.UseAdvancedDyes = v); - Checkbox("Always Apply Associated Mods", - "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n" - + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" - + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", + Checkbox("Always Apply Associated Mods"u8, + "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"u8 + + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 + + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault."u8, config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); - Checkbox("Use Temporary Mod Settings", - "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings, + Checkbox("Use Temporary Mod Settings"u8, + "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); ImGui.NewLine(); } @@ -117,33 +116,34 @@ public class SettingsTab( if (!ImUtf8.CollapsingHeader("Design Defaults")) return; - Checkbox("Show in Quick Design Bar", "Newly created designs will be shown in the quick design bar by default.", + Checkbox("Show in Quick Design Bar"u8, "Newly created designs will be shown in the quick design bar by default."u8, config.DefaultDesignSettings.ShowQuickDesignBar, v => config.DefaultDesignSettings.ShowQuickDesignBar = v); - Checkbox("Reset Advanced Dyes", "Newly created designs will be configured to reset advanced dyes on application by default.", + Checkbox("Reset Advanced Dyes"u8, "Newly created designs will be configured to reset advanced dyes on application by default."u8, config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v); - Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.", + Checkbox("Always Force Redraw"u8, "Newly created designs will be configured to force character redraws on application by default."u8, config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v); - Checkbox("Reset Temporary Settings", "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default.", + Checkbox("Reset Temporary Settings"u8, + "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default."u8, config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v); } private void DrawInterfaceSettings() { - if (!ImGui.CollapsingHeader("Interface")) + if (!ImUtf8.CollapsingHeader("Interface"u8)) return; - EphemeralCheckbox("Show Quick Design Bar", - "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", + EphemeralCheckbox("Show Quick Design Bar"u8, + "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target."u8, config.Ephemeral.ShowDesignQuickBar, v => config.Ephemeral.ShowDesignQuickBar = v); - EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", + EphemeralCheckbox("Lock Quick Design Bar"u8, "Prevent the quick design bar from being moved and lock it in place."u8, config.Ephemeral.LockDesignQuickBar, v => config.Ephemeral.LockDesignQuickBar = v); if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", 100 * ImGuiHelpers.GlobalScale, config.ToggleQuickDesignBar, v => config.ToggleQuickDesignBar = v, _validKeys)) config.Save(); - Checkbox("Show Quick Design Bar in Main Window", - "Show the quick design bar in the tab selection part of the main window, too.", + Checkbox("Show Quick Design Bar in Main Window"u8, + "Show the quick design bar in the tab selection part of the main window, too."u8, config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); DrawQuickDesignBoxes(); @@ -151,7 +151,7 @@ public class SettingsTab( ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.", + Checkbox("Enable Game Context Menus"u8, "Whether to show a Try On via Glamourer button on context menus for equippable items."u8, config.EnableGameContextMenu, v => { config.EnableGameContextMenu = v; @@ -160,41 +160,41 @@ public class SettingsTab( else contextMenuService.Disable(); }); - Checkbox("Show Window when UI is Hidden", "Whether to show Glamourer windows even when the games UI is hidden.", + Checkbox("Show Window when UI is Hidden"u8, "Whether to show Glamourer windows even when the games UI is hidden."u8, config.ShowWindowWhenUiHidden, v => { config.ShowWindowWhenUiHidden = v; uiBuilder.DisableUserUiHide = v; }); - Checkbox("Hide Window in Cutscenes", "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not.", + Checkbox("Hide Window in Cutscenes"u8, "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, config.HideWindowInCutscene, v => { config.HideWindowInCutscene = v; uiBuilder.DisableCutsceneUiHide = !v; }); - EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", + EphemeralCheckbox("Lock Main Window"u8, "Prevent the main window from being moved and lock it in place."u8, config.Ephemeral.LockMainWindow, v => config.Ephemeral.LockMainWindow = v); - Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", + Checkbox("Open Main Window at Game Start"u8, "Whether the main Glamourer window should be open or closed after launching the game."u8, config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", + Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8, config.SmallEquip, v => config.SmallEquip = v); DrawHeightUnitSettings(); - Checkbox("Show Application Checkboxes", - "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules.", + Checkbox("Show Application Checkboxes"u8, + "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8, !config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v); if (Widget.DoubleModifierSelector("Design Deletion Modifier", "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, config.DeleteDesignModifier, v => config.DeleteDesignModifier = v)) config.Save(); DrawRenameSettings(); - Checkbox("Auto-Open Design Folders", - "Have design folders open or closed as their default state after launching.", config.OpenFoldersByDefault, + Checkbox("Auto-Open Design Folders"u8, + "Have design folders open or closed as their default state after launching."u8, config.OpenFoldersByDefault, v => config.OpenFoldersByDefault = v); DrawFolderSortType(); @@ -202,32 +202,32 @@ public class SettingsTab( ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Allow Double-Clicking Designs to Apply", - "Tries to apply a design to the current player character When double-clicking it in the design selector.", + Checkbox("Allow Double-Clicking Designs to Apply"u8, + "Tries to apply a design to the current player character When double-clicking it in the design selector."u8, config.AllowDoubleClickToApply, v => config.AllowDoubleClickToApply = v); - Checkbox("Show all Application Rule Checkboxes for Automation", - "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", + Checkbox("Show all Application Rule Checkboxes for Automation"u8, + "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling."u8, config.ShowAllAutomatedApplicationRules, v => config.ShowAllAutomatedApplicationRules = v); - Checkbox("Show Unobtained Item Warnings", - "Show information whether you have unlocked all items and customizations in your automated design or not.", + Checkbox("Show Unobtained Item Warnings"u8, + "Show information whether you have unlocked all items and customizations in your automated design or not."u8, config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); if (config.UseAdvancedParameters) { - Checkbox("Show Color Display Config", "Show the Color Display configuration options in the Advanced Customization panels.", + Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, config.ShowColorConfig, v => config.ShowColorConfig = v); - Checkbox("Show Palette+ Import Button", - "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", + Checkbox("Show Palette+ Import Button"u8, + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); using var id = ImRaii.PushId(1); PaletteImportButton(); } if (config.UseAdvancedDyes) - Checkbox("Keep Advanced Dye Window Attached", - "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable.", + Checkbox("Keep Advanced Dye Window Attached"u8, + "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); - Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode, + Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, config.DebugMode, v => config.DebugMode = v); ImGui.NewLine(); } @@ -236,40 +236,48 @@ public class SettingsTab( { var showAuto = config.EnableAutoDesigns; var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 7 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1); + var numColumns = 8 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); - ImGui.TextUnformatted("Show the Following Buttons in the Quick Design Bar:"); + ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); - using var table = ImRaii.Table("##tableQdb", numColumns, + using var table = ImUtf8.Table("##tableQdb"u8, numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders | ImGuiTableFlags.NoHostExtendX); if (!table) return; - var columns = new[] - { - (" Apply Design ", true, QdbButtons.ApplyDesign), - (" Revert All ", true, QdbButtons.RevertAll), - (" Revert to Auto ", showAuto, QdbButtons.RevertAutomation), - (" Reapply Auto ", showAuto, QdbButtons.ReapplyAutomation), - (" Revert Equip ", true, QdbButtons.RevertEquip), - (" Revert Customize ", true, QdbButtons.RevertCustomize), - (" Revert Advanced ", showAdvanced, QdbButtons.RevertAdvanced), - }; + ReadOnlySpan<(string, bool, QdbButtons)> columns = + [ + ("Apply Design", true, QdbButtons.ApplyDesign), + ("Revert All", true, QdbButtons.RevertAll), + ("Revert to Auto", showAuto, QdbButtons.RevertAutomation), + ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), + ("Revert Equip", true, QdbButtons.RevertEquip), + ("Revert Customize", true, QdbButtons.RevertCustomize), + ("Revert Advanced", showAdvanced, QdbButtons.RevertAdvanced), + ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), + ]; - foreach (var (label, _, _) in columns.Where(t => t.Item2)) + for(var i = 0; i < columns.Length; ++i) { + if (!columns[i].Item2) + continue; + ImGui.TableNextColumn(); - ImGui.TableHeader(label); + ImUtf8.TableHeader(columns[i].Item1); } - foreach (var (_, _, flag) in columns.Where(t => t.Item2)) + for (var i = 0; i < columns.Length; ++i) { - using var id = ImRaii.PushId((int)flag); + if (!columns[i].Item2) + continue; + + var flag = columns[i].Item3; + using var id = ImUtf8.PushId((int)flag); ImGui.TableNextColumn(); var offset = (ImGui.GetContentRegionAvail().X - ImGui.GetFrameHeight()) / 2; ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offset); var value = config.QdbButtons.HasFlag(flag); - if (!ImGui.Checkbox(string.Empty, ref value)) + if (!ImUtf8.Checkbox(""u8, ref value)) continue; var buttons = value ? config.QdbButtons | flag : config.QdbButtons & ~flag; @@ -287,31 +295,31 @@ public class SettingsTab( return; ImGui.SameLine(); - if (ImGui.Button("Import Palette+ to Designs")) + if (ImUtf8.Button("Import Palette+ to Designs"u8)) paletteImport.ImportDesigns(); - ImGuiUtil.HoverTooltip( + ImUtf8.HoverTooltip( $"Import all existing Palettes from your Palette+ Config into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); } /// Draw the entire Color subsection. private void DrawColorSettings() { - if (!ImGui.CollapsingHeader("Colors")) + if (!ImUtf8.CollapsingHeader("Colors"u8)) return; - using (var tree = ImRaii.TreeNode("Custom Design Colors")) + using (var tree = ImUtf8.TreeNode("Custom Design Colors"u8)) { if (tree) designColorUi.Draw(); } - using (var tree = ImRaii.TreeNode("Color Settings")) + using (var tree = ImUtf8.TreeNode("Color Settings"u8)) { if (tree) foreach (var color in Enum.GetValues()) { var (defaultColor, name, description) = color.Data(); - var currentColor = config.Colors.TryGetValue(color, out var current) ? current : defaultColor; + var currentColor = config.Colors.GetValueOrDefault(color, defaultColor); if (Widget.ColorPicker(name, description, currentColor, c => config.Colors[color] = c, defaultColor)) config.Save(); } @@ -321,33 +329,33 @@ public class SettingsTab( } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void Checkbox(string label, string tooltip, bool current, Action setter) + private void Checkbox(ReadOnlySpan label, ReadOnlySpan tooltip, bool current, Action setter) { - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var tmp = current; - if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) + if (ImUtf8.Checkbox(""u8, ref tmp) && tmp != current) { setter(tmp); config.Save(); } ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(label, tooltip); + ImUtf8.LabeledHelpMarker(label, tooltip); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void EphemeralCheckbox(string label, string tooltip, bool current, Action setter) + private void EphemeralCheckbox(ReadOnlySpan label, ReadOnlySpan tooltip, bool current, Action setter) { - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var tmp = current; - if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) + if (ImUtf8.Checkbox(""u8, ref tmp) && tmp != current) { setter(tmp); config.Ephemeral.Save(); } ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(label, tooltip); + ImUtf8.LabeledHelpMarker(label, tooltip); } /// Different supported sort modes as a combo. @@ -355,29 +363,29 @@ public class SettingsTab( { var sortMode = config.SortMode; ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##sortMode", sortMode.Name)) + using (var combo = ImUtf8.Combo("##sortMode"u8, sortMode.Name)) { if (combo) foreach (var val in Configuration.Constants.ValidSortModes) { - if (ImGui.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) + if (ImUtf8.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) { config.SortMode = val; selector.SetFilterDirty(); config.Save(); } - ImGuiUtil.HoverTooltip(val.Description); + ImUtf8.HoverTooltip(val.Description); } } - ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the designs tab."); + ImUtf8.LabeledHelpMarker("Sort Mode"u8, "Choose the sort mode for the mod selector in the designs tab."u8); } private void DrawRenameSettings() { ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##renameSettings", config.ShowRename.GetData().Name)) + using (var combo = ImUtf8.Combo("##renameSettings"u8, config.ShowRename.GetData().Name)) { if (combo) foreach (var value in Enum.GetValues()) @@ -390,7 +398,7 @@ public class SettingsTab( config.Save(); } - ImGuiUtil.HoverTooltip(desc); + ImUtf8.HoverTooltip(desc); } } @@ -399,19 +407,19 @@ public class SettingsTab( "Select which of the two renaming input fields are visible when opening the right-click context menu of a design in the design selector."; ImGuiComponents.HelpMarker(tt); ImGui.SameLine(); - ImGui.TextUnformatted("Rename Fields in Design Context Menu"); - ImGuiUtil.HoverTooltip(tt); + ImUtf8.Text("Rename Fields in Design Context Menu"u8); + ImUtf8.HoverTooltip(tt); } private void DrawHeightUnitSettings() { ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##heightUnit", HeightDisplayTypeName(config.HeightDisplayType))) + using (var combo = ImUtf8.Combo("##heightUnit"u8, HeightDisplayTypeName(config.HeightDisplayType))) { if (combo) foreach (var type in Enum.GetValues()) { - if (ImGui.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType) + if (ImUtf8.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType) { config.HeightDisplayType = type; config.Save(); @@ -423,20 +431,20 @@ public class SettingsTab( const string tt = "Select how to display the height of characters in real-world units, if at all."; ImGuiComponents.HelpMarker(tt); ImGui.SameLine(); - ImGui.TextUnformatted("Character Height Display Type"); - ImGuiUtil.HoverTooltip(tt); + ImUtf8.Text("Character Height Display Type"u8); + ImUtf8.HoverTooltip(tt); } - private static string HeightDisplayTypeName(HeightDisplayType type) + private static ReadOnlySpan HeightDisplayTypeName(HeightDisplayType type) => type switch { - HeightDisplayType.None => "Do Not Display", - HeightDisplayType.Centimetre => "Centimetres (000.0 cm)", - HeightDisplayType.Metre => "Metres (0.00 m)", - HeightDisplayType.Wrong => "Inches (00.0 in)", - HeightDisplayType.WrongFoot => "Feet (0'00'')", - HeightDisplayType.Corgi => "Corgis (0.0 Corgis)", - HeightDisplayType.OlympicPool => "Olympic-size swimming Pools (0.000 Pools)", - _ => string.Empty, + HeightDisplayType.None => "Do Not Display"u8, + HeightDisplayType.Centimetre => "Centimetres (000.0 cm)"u8, + HeightDisplayType.Metre => "Metres (0.00 m)"u8, + HeightDisplayType.Wrong => "Inches (00.0 in)"u8, + HeightDisplayType.WrongFoot => "Feet (0'00'')"u8, + HeightDisplayType.Corgi => "Corgis (0.0 Corgis)"u8, + HeightDisplayType.OlympicPool => "Olympic-size swimming Pools (0.000 Pools)"u8, + _ => ""u8, }; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 42058d2..fec5b13 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -407,7 +407,8 @@ public class StateEditor( } else if (!value.Revert) { - Editor.ChangeMaterialValue(state, idx, new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), + Editor.ChangeMaterialValue(state, idx, + new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), settings.Source, out _, settings.Key); } } diff --git a/OtterGui b/OtterGui index 0b6085c..332852f 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 0b6085ce720ffb7c78cf42d4e51861f34db27744 +Subproject commit 332852ffa81387b59f260781d7e5c967f24d8be2 From e5b2114ac250dec49e052ffc15ceae9955c0af4d Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 18 Feb 2025 14:13:13 +0000 Subject: [PATCH 053/212] [CI] Updating repo.json for testing_1.3.6.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 76009f4..992ca6a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.1", + "TestingAssemblyVersion": "1.3.6.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 9e7679e70fecce6aa8abdb1a7b9eb769bd7a58e7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 19 Feb 2025 23:36:52 +0100 Subject: [PATCH 054/212] Move check for valid model to actor display instead of identifier list. --- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 7 ++++--- Glamourer/Interop/ObjectManager.cs | 2 +- OtterGui | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 76f0ba4..3269fd2 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -91,9 +91,10 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral objects.Update(); _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers, skips, CheckFilter, DrawSelectable); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); + var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, + DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index b185f4a..47f7ad5 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -83,7 +83,7 @@ public class ObjectManager( private void HandleIdentifier(ActorIdentifier identifier, Actor character) { - if (!character.Model || !identifier.IsValid) + if (!identifier.IsValid) return; if (!_identifiers.TryGetValue(identifier, out var data)) diff --git a/OtterGui b/OtterGui index 332852f..06422a8 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 332852ffa81387b59f260781d7e5c967f24d8be2 +Subproject commit 06422a893348a18a013e6dbc558370db8d21a446 From 5a9e9513f4cf62fff84237a6a68717e9d5857693 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 19 Feb 2025 23:42:05 +0100 Subject: [PATCH 055/212] Skip automatic updates from temporary settings when applied by design. --- .../Interop/Penumbra/ModSettingApplier.cs | 5 +++-- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 21 +++++++++++-------- .../Penumbra/PenumbraAutoRedrawSkip.cs | 15 +++++++++++++ Glamourer/State/StateEditor.cs | 2 +- 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 5b27e6e..60f07e4 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -7,12 +7,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ObjectManager objects, CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; - public void HandleStateApplication(ActorState state, MergedDesign design) + public void HandleStateApplication(ActorState state, MergedDesign design, bool skipAutoRedraw) { if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; @@ -26,6 +26,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O } _collectionTracker.Clear(); + using var skip = autoRedrawSkip.SkipAutoUpdates(skipAutoRedraw); foreach (var actor in data.Objects) { var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 3e48fe9..93d23c2 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -12,16 +12,18 @@ namespace Glamourer.Interop.Penumbra; public class PenumbraAutoRedraw : IDisposable, IRequiredService { - private const int WaitFrames = 5; - private readonly Configuration _config; - private readonly PenumbraService _penumbra; - private readonly StateManager _state; - private readonly ObjectManager _objects; - private readonly IFramework _framework; - private readonly StateChanged _stateChanged; + private const int WaitFrames = 5; + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly StateManager _state; + private readonly ObjectManager _objects; + private readonly IFramework _framework; + private readonly StateChanged _stateChanged; + private readonly PenumbraAutoRedrawSkip _skip; + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework, - StateChanged stateChanged) + StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { _penumbra = penumbra; _config = config; @@ -29,6 +31,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _objects = objects; _framework = framework; _stateChanged = stateChanged; + _skip = skip; _penumbra.ModSettingChanged += OnModSettingChange; _framework.Update += OnFramework; _stateChanged.Subscribe(OnStateChanged, StateChanged.Priority.PenumbraAutoRedraw); @@ -94,7 +97,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } }); } - else if (_config.AutoRedrawEquipOnChanges) + else if (_config.AutoRedrawEquipOnChanges && !_skip.Skip) { // Only update once per frame. var playerName = _penumbra.GetCurrentPlayerCollection(); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs new file mode 100644 index 0000000..8ef522c --- /dev/null +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs @@ -0,0 +1,15 @@ +using OtterGui.Classes; +using OtterGui.Services; + +namespace Glamourer.Interop.Penumbra; + +public class PenumbraAutoRedrawSkip : IService +{ + private bool _skipAutoUpdates; + + public BoolSetter SkipAutoUpdates(bool skip) + => new(ref _skipAutoUpdates, skip); + + public bool Skip + => _skipAutoUpdates; +} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index fec5b13..7eea607 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -262,7 +262,7 @@ public class StateEditor( public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { var state = (ActorState)data; - modApplier.HandleStateApplication(state, mergedDesign); + modApplier.HandleStateApplication(state, mergedDesign, true); if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; From c1f84b4303b76f935a1f6023f12308868dc4a00a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 20 Feb 2025 00:16:49 +0100 Subject: [PATCH 056/212] Differentiate between temporary settings through manual and automatic application. --- Glamourer/Automation/AutoDesignApplier.cs | 10 ++- Glamourer/Gui/DesignQuickBar.cs | 9 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 77 +++++++++---------- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 5 +- Glamourer/Interop/ObjectManager.cs | 10 ++- .../Interop/Penumbra/ModSettingApplier.cs | 26 ++++--- Glamourer/Interop/Penumbra/PenumbraService.cs | 44 +++++++---- Glamourer/Services/CommandService.cs | 2 +- Glamourer/State/StateEditor.cs | 2 +- 9 files changed, 108 insertions(+), 77 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 7f75674..545dff7 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -293,8 +293,16 @@ public sealed class AutoDesignApplier : IDisposable set.Designs.Where(d => d.IsActive(actor)) .SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); - if (set.ResetTemporarySettings) + + if (_objects.IsInGPose && actor.IsGPoseOrCutscene) + { + mergedDesign.ResetTemporarySettings = false; + mergedDesign.AssociatedMods.Clear(); + } + else if (set.ResetTemporarySettings) + { mergedDesign.ResetTemporarySettings = true; + } _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); forcedRedraw = mergedDesign.ForcedRedraw; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 1e2904c..b125b37 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -409,7 +409,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_playerIdentifier}."; + tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_playerIdentifier}."; } if (_targetIdentifier.IsValid && _targetData.Valid) @@ -417,7 +417,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_targetIdentifier}."; + tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_targetIdentifier}."; } if (available == 0) @@ -426,7 +426,10 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index); + { + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Manual); + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Fixed); + } } private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index caa7d60..680e0e9 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -52,7 +52,7 @@ public class SetPanel( private void DrawPanel() { - using var child = ImRaii.Child("##Panel", -Vector2.One, true); + using var child = ImUtf8.Child("##Panel"u8, -Vector2.One, true); if (!child || !_selector.HasSelection) return; @@ -63,20 +63,20 @@ public class SetPanel( using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var enabled = Selection.Enabled; - if (ImGui.Checkbox("##Enabled", ref enabled)) + if (ImUtf8.Checkbox("##Enabled"u8, ref enabled)) _manager.SetState(_selector.SelectionIndex, enabled); - ImGuiUtil.LabeledHelpMarker("Enabled", - "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); + ImUtf8.LabeledHelpMarker("Enabled"u8, + "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."u8); } using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; - if (ImGui.Checkbox("##gameState", ref useGame)) + if (ImUtf8.Checkbox("##gameState"u8, ref useGame)) _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); - ImGuiUtil.LabeledHelpMarker("Use Game State as Base", - "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " - + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); + ImUtf8.LabeledHelpMarker("Use Game State as Base"u8, + "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "u8 + + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."u8); } } @@ -86,14 +86,14 @@ public class SetPanel( using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var editing = _config.ShowAutomationSetEditing; - if (ImGui.Checkbox("##Show Editing", ref editing)) + if (ImUtf8.Checkbox("##Show Editing"u8, ref editing)) { _config.ShowAutomationSetEditing = editing; _config.Save(); } - ImGuiUtil.LabeledHelpMarker("Show Editing", - "Show options to change the name or the associated character or NPC of this design set."); + ImUtf8.LabeledHelpMarker("Show Editing"u8, + "Show options to change the name or the associated character or NPC of this design set."u8); } using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) @@ -102,8 +102,8 @@ public class SetPanel( if (ImGui.Checkbox("##resetSettings", ref resetSettings)) _manager.ChangeResetSettings(_selector.SelectionIndex, resetSettings); - ImGuiUtil.LabeledHelpMarker("Reset Temporary Settings", - "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."); + ImUtf8.LabeledHelpMarker("Reset Temporary Settings"u8, + "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."u8); } } @@ -160,42 +160,42 @@ public class SetPanel( (false, false) => 4, }; - using var table = ImRaii.Table("SetTable", numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY); + using var table = ImUtf8.Table("SetTable"u8, numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY); if (!table) return; - ImGui.TableSetupColumn("##del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); - ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("##del"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); + ImUtf8.TableSetupColumn("##Index"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); if (singleRow) { - ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design"u8, ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale); if (_config.ShowAllAutomatedApplicationRules) - ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, + ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 6 * ImGui.GetFrameHeight() + 10 * ImGuiHelpers.GlobalScale); else - ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); + ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); } else { - ImGui.TableSetupColumn("Design / Job Restrictions", ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); if (_config.ShowAllAutomatedApplicationRules) - ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, + ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); else - ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); + ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); } if (singleRow) - ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Job Restrictions"u8, ImGuiTableColumnFlags.WidthStretch); if (_config.ShowUnlockedItemWarnings) - ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn(""u8, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); foreach (var (design, idx) in Selection.Designs.WithIndex()) { - using var id = ImRaii.PushId(idx); + using var id = ImUtf8.PushId(idx); ImGui.TableNextColumn(); var keyValid = _config.DeleteDesignModifier.IsActive(); var tt = keyValid @@ -205,7 +205,7 @@ public class SetPanel( if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true)) _endAction = () => _manager.DeleteDesign(Selection, idx); ImGui.TableNextColumn(); - ImGui.Selectable($"#{idx + 1:D2}"); + ImUtf8.Selectable($"#{idx + 1:D2}"); DrawDragDrop(Selection, idx); ImGui.TableNextColumn(); DrawRandomEditing(Selection, design, idx); @@ -234,8 +234,7 @@ public class SetPanel( ImGui.TableNextColumn(); ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("New"); + ImUtf8.TextFrameAligned("New"u8); ImGui.TableNextColumn(); _designCombo.Draw(Selection, null, -1); ImGui.TableNextRow(); @@ -250,20 +249,20 @@ public class SetPanel( private void DrawConditions(AutoDesign design, int idx) { var usingGearset = design.GearsetIndex >= 0; - if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) + if (ImUtf8.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) { usingGearset = !usingGearset; _manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1)); } - ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions."); + ImUtf8.HoverTooltip("Click to switch between Job and Gearset restrictions."u8); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); if (usingGearset) { var set = 1 + (_tmpGearset == int.MaxValue || _whichIndex != idx ? design.GearsetIndex : _tmpGearset); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputInt("##whichGearset", ref set, 0, 0)) + if (ImUtf8.InputScalar("##whichGearset"u8, ref set)) { _whichIndex = idx; _tmpGearset = Math.Clamp(set, 1, 100); @@ -361,12 +360,12 @@ public class SetPanel( ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); } - ImGuiUtil.HoverTooltip(sb.ToString()); + ImUtf8.HoverTooltip($"{sb}"); } else { ImGuiUtil.DrawTextButton(string.Empty, size, 0); - ImGuiUtil.HoverTooltip(good); + ImUtf8.HoverTooltip(good); } } } @@ -374,7 +373,7 @@ public class SetPanel( private void DrawDragDrop(AutoDesignSet set, int index) { const string dragDropLabel = "DesignDragDrop"; - using (var target = ImRaii.DragDropTarget()) + using (var target = ImUtf8.DragDropTarget()) { if (target.Success && ImGuiUtil.IsDropping(dragDropLabel)) { @@ -388,11 +387,11 @@ public class SetPanel( } } - using (var source = ImRaii.DragDropSource()) + using (var source = ImUtf8.DragDropSource()) { if (source) { - ImGui.TextUnformatted($"Moving design #{index + 1:D2}..."); + ImUtf8.Text($"Moving design #{index + 1:D2}..."); if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) { _dragIndex = index; @@ -415,16 +414,16 @@ public class SetPanel( } style.Pop(); - ImGuiUtil.HoverTooltip("Toggle all application modes at once."); + ImUtf8.HoverTooltip("Toggle all application modes at once."u8); if (_config.ShowAllAutomatedApplicationRules) { void Box(int idx) { var (type, description) = ApplicationTypeExtensions.Types[idx]; var value = design.Type.HasFlag(type); - if (ImGui.Checkbox($"##{(byte)type}", ref value)) + if (ImUtf8.Checkbox($"##{(byte)type}", ref value)) newType = value ? newType | type : newType & ~type; - ImGuiUtil.HoverTooltip(description); + ImUtf8.HoverTooltip(description); } ImGui.SameLine(); diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index feff657..5856fcc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -4,6 +4,7 @@ using Dalamud.Interface.Utility; using Dalamud.Utility; using Glamourer.Designs; using Glamourer.Interop.Penumbra; +using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -83,7 +84,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void ApplyAll() { foreach (var (mod, settings) in selector.Selected!.AssociatedMods) - penumbra.SetMod(mod, settings); + penumbra.SetMod(mod, settings, StateSource.Manual); } private void DrawTable() @@ -218,7 +219,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) { - var text = penumbra.SetMod(mod, settings); + var text = penumbra.SetMod(mod, settings, StateSource.Manual); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 47f7ad5..471a49b 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; +using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Log; using Penumbra.GameData.Actors; @@ -24,8 +25,11 @@ public class ObjectManager( => LastFrame; private DateTime _identifierUpdate; - public bool IsInGPose { get; private set; } - public ushort World { get; private set; } + + public bool IsInGPose + => clientState.IsGPosing; + + public ushort World { get; private set; } private readonly Dictionary _identifiers = new(200); private readonly Dictionary _allWorldIdentifiers = new(200); @@ -76,8 +80,6 @@ public class ObjectManager( HandleIdentifier(identifier, actor); } - var gPose = GPosePlayer; - IsInGPose = gPose.Utf8Name.Length > 0; return true; } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 60f07e4..82a840c 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -12,7 +12,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip { private readonly HashSet _collectionTracker = []; - public void HandleStateApplication(ActorState state, MergedDesign design, bool skipAutoRedraw) + public void HandleStateApplication(ActorState state, MergedDesign design, StateSource source, bool skipAutoRedraw, bool respectManual) { if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; @@ -36,10 +36,10 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip if (!_collectionTracker.Add(collection)) continue; - var index = ResetOldSettings(collection, actor, design.ResetTemporarySettings); + var index = ResetOldSettings(collection, actor, source, design.ResetTemporarySettings, respectManual); foreach (var (mod, setting) in design.AssociatedMods) { - var message = penumbra.SetMod(mod, setting, collection, index); + var message = penumbra.SetMod(mod, setting, source, collection, index); if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else @@ -50,7 +50,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip } public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings( - IReadOnlyDictionary settings, Actor actor, bool resetOther) + IReadOnlyDictionary settings, Actor actor, StateSource source, bool resetOther) { var (collection, name, overridden) = overrides.GetCollection(actor); if (collection == Guid.Empty) @@ -59,10 +59,10 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var messages = new List(); var appliedMods = 0; - var index = ResetOldSettings(collection, actor, resetOther); + var index = ResetOldSettings(collection, actor, source, resetOther, true); foreach (var (mod, setting) in settings) { - var message = penumbra.SetMod(mod, setting, collection, index); + var message = penumbra.SetMod(mod, setting, source, collection, index); if (message.Length > 0) messages.Add($"Error applying mod settings: {message}"); else @@ -73,16 +73,24 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, bool resetOther) + private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, StateSource source, bool resetOther, bool respectManual) { ObjectIndex? index = actor.Valid ? actor.Index : null; if (!resetOther) return index; if (index == null) - penumbra.RemoveAllTemporarySettings(collection); + { + penumbra.RemoveAllTemporarySettings(collection, source); + if (!respectManual && source.IsFixed()) + penumbra.RemoveAllTemporarySettings(collection, StateSource.Manual); + } else - penumbra.RemoveAllTemporarySettings(index.Value); + { + penumbra.RemoveAllTemporarySettings(index.Value, source); + if (!respectManual && source.IsFixed()) + penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual); + } return index; } } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index e0c445b..d9b4d27 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; using Glamourer.Events; +using Glamourer.State; using OtterGui.Classes; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; @@ -40,7 +41,10 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp3 = 6; public const int RequiredPenumbraFeatureVersionTemp4 = 7; - private const int Key = -1610; + private const int KeyFixed = -1610; + private const string NameFixed = "Glamourer (Automation)"; + private const int KeyManual = -6160; + private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; private readonly Configuration _config; @@ -160,7 +164,7 @@ public class PenumbraService : IDisposable if (_getCurrentSettingsWithTemp != null) { source = string.Empty; - var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, Key); + var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, KeyFixed); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -216,7 +220,7 @@ public class PenumbraService : IDisposable { if (_getAllSettings != null) { - var allSettings = _getAllSettings.Invoke(collection, false, false, Key); + var allSettings = _getAllSettings.Invoke(collection, false, false, KeyFixed); if (allSettings.Item1 is PenumbraApiEc.Success) return mods.Select(m => (new Mod(m.Value, m.Key), allSettings.Item2!.TryGetValue(m.Key, out var s) @@ -276,7 +280,7 @@ public class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null, ObjectIndex? index = null) + public string SetMod(Mod mod, ModSettings settings, StateSource source, Guid? collectionInput = null, ObjectIndex? index = null) { if (!Available) return "Penumbra is not available."; @@ -286,7 +290,7 @@ public class PenumbraService : IDisposable { var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; if (_config.UseTemporarySettings && _setTemporaryModSettings != null) - SetModTemporary(sb, mod, settings, collection, index); + SetModTemporary(sb, mod, settings, collection, index, source); else SetModPermanent(sb, mod, settings, collection); @@ -298,37 +302,43 @@ public class PenumbraService : IDisposable } } - public void RemoveAllTemporarySettings(Guid collection) - => _removeAllTemporaryModSettings?.Invoke(collection, Key); + public void RemoveAllTemporarySettings(Guid collection, StateSource source) + => _removeAllTemporaryModSettings?.Invoke(collection, source.IsFixed() ? KeyFixed : KeyManual); - public void RemoveAllTemporarySettings(ObjectIndex index) - => _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, Key); + public void RemoveAllTemporarySettings(ObjectIndex index, StateSource source) + => _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, source.IsFixed() ? KeyFixed : KeyManual); - public void ClearAllTemporarySettings() + public void ClearAllTemporarySettings(bool fix, bool manual) { if (!Available || _removeAllTemporaryModSettings == null) return; var collections = _collections!.Invoke(); foreach (var collection in collections) - RemoveAllTemporarySettings(collection.Key); + { + if (fix) + RemoveAllTemporarySettings(collection.Key, StateSource.Fixed); + if (manual) + RemoveAllTemporarySettings(collection.Key, StateSource.Manual); + } } public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; - private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index) + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index, StateSource source) { + var (key, name) = source.IsFixed() ? (KeyFixed, NameFixed) : (KeyManual, NameManual); var ex = settings.Remove ? index.HasValue - ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, Key) - : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, Key) + ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) + : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key) : index.HasValue ? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key) + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key) : _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key); + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key); switch (ex) { case PenumbraApiEc.InvalidArgument: @@ -586,7 +596,7 @@ public class PenumbraService : IDisposable public void Dispose() { - ClearAllTemporarySettings(); + ClearAllTemporarySettings(true, true); Unattach(); _tooltipSubscriber.Dispose(); _clickSubscriber.Dispose(); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index bffc072..4b8ea3d 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -694,7 +694,7 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, d.ResetTemporarySettings); + var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 7eea607..1fa1ffe 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -262,7 +262,7 @@ public class StateEditor( public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { var state = (ActorState)data; - modApplier.HandleStateApplication(state, mergedDesign, true); + modApplier.HandleStateApplication(state, mergedDesign, settings.Source, true, settings.RespectManual); if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; From 8a7ec45bbf215602b82b94970e9d8c5a80761e0a Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 19 Feb 2025 23:19:21 +0000 Subject: [PATCH 057/212] [CI] Updating repo.json for testing_1.3.6.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 992ca6a..31b5654 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.2", + "TestingAssemblyVersion": "1.3.6.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0c8110e15e0f2200c9d95a27485f3bf50a3e6df4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 21 Feb 2025 00:10:30 +0100 Subject: [PATCH 058/212] Highlight existing advanced dyes in state and design. --- Glamourer/Gui/Colors.cs | 2 + Glamourer/Gui/Equipment/BonusDrawData.cs | 6 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 14 +-- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 89 +++++++++++-------- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 35 ++++---- .../Interop/Material/MaterialValueManager.cs | 20 +++++ 6 files changed, 106 insertions(+), 60 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index e19639c..98deace 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -32,6 +32,7 @@ public enum ColorId ModdedItemMarker, ContainsItemsEnabled, ContainsItemsDisabled, + AdvancedDyeActive, } public static class Colors @@ -70,6 +71,7 @@ public static class Colors ColorId.ModdedItemMarker => (0xFFFF20FF, "Modded Item Marker", "The color of dot in the unlocks overview tab signaling that the item is modded in the currently selected Penumbra collection." ), ColorId.ContainsItemsEnabled => (0xFFA0F0A0, "Enabled Mod Contains Design Items", "The color of enabled mods in the associated mod dropdown menu when they contain items used in this design." ), ColorId.ContainsItemsDisabled => (0x80A0F0A0, "Disabled Mod Contains Design Items", "The color of disabled mods in the associated mod dropdown menu when they contain items used in this design." ), + ColorId.AdvancedDyeActive => (0xFF58DDFF, "Advanced Dyes Active", "The highlight color for the advanced dye button and marker if any advanced dyes are active for this slot." ), _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index e19287a..c14de2a 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Interop.Material; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,10 +10,11 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) { private IDesignEditor _editor = null!; private object _object = null!; - public readonly BonusItemFlag Slot = slot; + public readonly BonusItemFlag Slot = slot; public bool Locked; public bool DisplayApplication; public bool AllowRevert; + public bool HasAdvancedDyes; public readonly bool IsDesign => _object is Design; @@ -42,6 +44,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) CurrentApply = design.DoApplyBonusItem(slot), Locked = design.WriteProtected(), DisplayApplication = true, + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), }; public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot) @@ -53,5 +56,6 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.BonusItem(slot), AllowRevert = true, + HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), }; } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index b72e234..d13b9e4 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Interop.Material; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,7 +10,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) { private IDesignEditor _editor = null!; private object _object = null!; - public readonly EquipSlot Slot = slot; + public readonly EquipSlot Slot = slot; public bool Locked; public bool DisplayApplication; public bool AllowRevert; @@ -29,15 +30,13 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetApplyItem(bool value) { var manager = (DesignManager)_editor; - var design = (Design)_object; - manager.ChangeApplyItem(design, Slot, value); + manager.ChangeApplyItem((Design)_object, Slot, value); } public readonly void SetApplyStain(bool value) { var manager = (DesignManager)_editor; - var design = (Design)_object; - manager.ChangeApplyStains(design, Slot, value); + manager.ChangeApplyStains((Design)_object, Slot, value); } public EquipItem CurrentItem = designData.Item(slot); @@ -46,6 +45,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public StainIds GameStains = default; public bool CurrentApply; public bool CurrentApplyStain; + public bool HasAdvancedDyes; public readonly Gender CurrentGender = designData.Customize.Gender; public readonly Race CurrentRace = designData.Customize.Race; @@ -58,6 +58,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) CurrentApply = design.DoApplyEquip(slot), CurrentApplyStain = design.DoApplyStain(slot), Locked = design.WriteProtected(), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), DisplayApplication = true, }; @@ -70,6 +71,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.Item(slot), GameStains = state.BaseData.Stain(slot), + HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), AllowRevert = true, }; -} \ No newline at end of file +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c8a4b11..2bd84e7 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; using Glamourer.Gui.Materials; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -63,6 +64,7 @@ public class EquipmentDrawer private Vector2 _iconSize; private float _comboLength; + private uint _advancedMaterialColor; public void Prepare() { @@ -74,7 +76,8 @@ public class EquipmentDrawer .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) / ImGuiHelpers.GlobalScale; - _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; + _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; + _advancedMaterialColor = ColorId.AdvancedDyeActive.Value(); } private bool VerifyRestrictedGear(EquipDrawData data) @@ -91,7 +94,7 @@ public class EquipmentDrawer if (_config.HideApplyCheckmarks) equipDrawData.DisplayApplication = false; - using var id = ImRaii.PushId((int)equipDrawData.Slot); + using var id = ImUtf8.PushId((int)equipDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -106,7 +109,7 @@ public class EquipmentDrawer if (_config.HideApplyCheckmarks) bonusDrawData.DisplayApplication = false; - using var id = ImRaii.PushId(100 + (int)bonusDrawData.Slot); + using var id = ImUtf8.PushId(100 + (int)bonusDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -127,7 +130,7 @@ public class EquipmentDrawer offhand.DisplayApplication = false; } - using var id = ImRaii.PushId("Weapons"); + using var id = ImUtf8.PushId("Weapons"u8); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -174,7 +177,7 @@ public class EquipmentDrawer change = true; } - ImGuiUtil.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear."); + ImUtf8.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear."); } return change; @@ -196,14 +199,13 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (VerifyRestrictedGear(equipDrawData)) label += " (Restricted)"; - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawBonusItemSmall(in BonusDrawData bonusDrawData) @@ -218,11 +220,10 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) @@ -239,12 +240,12 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (allWeapons) mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; - WeaponHelpMarker(mainhandLabel); + WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel); if (offhand.CurrentItem.Type is FullEquipType.Unknown) return; @@ -261,10 +262,10 @@ public class EquipmentDrawer } else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - WeaponHelpMarker(offhandLabel); + WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel); } #endregion @@ -285,8 +286,8 @@ public class EquipmentDrawer DrawApply(equipDrawData); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); + DrawStain(equipDrawData, false); if (equipDrawData.DisplayApplication) { @@ -295,13 +296,13 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (VerifyRestrictedGear(equipDrawData)) { ImGui.SameLine(); - ImGui.TextUnformatted("(Restricted)"); + ImUtf8.Text("(Restricted)"u8); } } @@ -319,11 +320,10 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) @@ -334,7 +334,7 @@ public class EquipmentDrawer mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (ImRaii.Group()) + using (ImUtf8.Group()) { DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left); if (mainhand.DisplayApplication) @@ -343,7 +343,8 @@ public class EquipmentDrawer DrawApply(mainhand); } - WeaponHelpMarker(mainhandLabel, allWeapons ? mainhand.CurrentItem.Type.ToName() : null); + WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel, + allWeapons ? mainhand.CurrentItem.Type.ToName() : null); DrawStain(mainhand, false); if (mainhand.DisplayApplication) @@ -353,7 +354,7 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } } @@ -364,7 +365,7 @@ public class EquipmentDrawer var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (ImRaii.Group()) + using (ImUtf8.Group()) { DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left); if (offhand.DisplayApplication) @@ -373,7 +374,7 @@ public class EquipmentDrawer DrawApply(offhand); } - WeaponHelpMarker(offhandLabel); + WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel); DrawStain(offhand, false); if (offhand.DisplayApplication) @@ -381,9 +382,9 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } - else if (mainhand.IsState) + else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } } } @@ -468,14 +469,16 @@ public class EquipmentDrawer UiHelpers.OpenCombo($"##{combo.Label}"); using var disabled = ImRaii.Disabled(data.Locked); - var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, + small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), out var item)) + if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), + out var item)) data.SetItem(item); } @@ -502,7 +505,7 @@ public class EquipmentDrawer (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), (false, false, _) => ("Control and mouse wheel to scroll.", default, false), }; - ImGuiUtil.HoverTooltip(tt); + ImUtf8.HoverTooltip(tt); return clicked && valid; } @@ -544,8 +547,8 @@ public class EquipmentDrawer } } - if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); + if (unknown) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) @@ -595,14 +598,14 @@ public class EquipmentDrawer #endregion - private static void WeaponHelpMarker(string label, string? type = null) + private void WeaponHelpMarker(bool hasAdvancedDyes, string label, string? type = null) { ImGui.SameLine(); ImGuiComponents.HelpMarker( "Changing weapons to weapons of different types can cause crashes, freezes, soft- and hard locks and cheating, " + "thus it is only allowed to change weapons to other weapons of the same type."); - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(hasAdvancedDyes, label); + if (type == null) return; @@ -610,4 +613,16 @@ public class EquipmentDrawer pos.Y += ImGui.GetFrameHeightWithSpacing(); ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private void DrawEquipLabel(bool hasAdvancedDyes, string label) + { + ImGui.SameLine(); + using (ImRaii.PushColor(ImGuiCol.Text, _advancedMaterialColor, hasAdvancedDyes)) + { + ImUtf8.Text(label); + } + if (hasAdvancedDyes) + ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8); + } } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index b842d7f..4fa93c1 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -51,28 +51,29 @@ public sealed unsafe class AdvancedDyePopup( return true; } - public void DrawButton(EquipSlot slot) - => DrawButton(MaterialValueIndex.FromSlot(slot)); + public void DrawButton(EquipSlot slot, uint color) + => DrawButton(MaterialValueIndex.FromSlot(slot), color); - public void DrawButton(BonusItemFlag slot) - => DrawButton(MaterialValueIndex.FromSlot(slot)); + public void DrawButton(BonusItemFlag slot, uint color) + => DrawButton(MaterialValueIndex.FromSlot(slot), color); - private void DrawButton(MaterialValueIndex index) + private void DrawButton(MaterialValueIndex index, uint color) { if (!config.UseAdvancedDyes) return; ImGui.SameLine(); - using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); + using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; - using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen) - .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen) - .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen)) + var (textColor, buttonColor) = isOpen + ? (ColorId.HeaderButtons.Value(), ImGui.GetColorU32(ImGuiCol.ButtonActive)) + : (color, 0u); + + using (ImRaii.PushColor(ImGuiCol.Border, textColor, isOpen)) { using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - string.Empty, false, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Palette, ""u8, default, false, textColor, buttonColor)) { _forceFocus = true; _selectedMaterial = byte.MaxValue; @@ -80,7 +81,7 @@ public sealed unsafe class AdvancedDyePopup( } } - ImGuiUtil.HoverTooltip("Open advanced dyes for this slot."); + ImUtf8.HoverTooltip("Open advanced dyes for this slot."u8); } private (string Path, string GamePath) ResourceName(MaterialValueIndex index) @@ -197,7 +198,7 @@ public sealed unsafe class AdvancedDyePopup( var width = 7 * ImGui.GetFrameHeight() // Buttons + 3 * ImGui.GetStyle().ItemSpacing.X // around text + 7 * ImGui.GetStyle().ItemInnerSpacing.X - + 200 * ImGuiHelpers.GlobalScale // Drags + + 200 * ImGuiHelpers.GlobalScale // Drags + 7 * UiBuilder.MonoFont.GetCharAdvance(' ') * ImGuiHelpers.GlobalScale // Row + 2 * ImGui.GetStyle().WindowPadding.X; var height = 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y; @@ -305,8 +306,9 @@ public sealed unsafe class AdvancedDyePopup( { EquipSlot.MainHand => _state.ModelData.Weapon(EquipSlot.MainHand), EquipSlot.OffHand => _state.ModelData.Weapon(EquipSlot.OffHand), - EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better - _ => _state.ModelData.Armor(slot).ToWeapon(0), + EquipSlot.Unknown => + _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better + _ => _state.ModelData.Armor(slot).ToWeapon(0), }; value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); } @@ -392,7 +394,8 @@ public sealed unsafe class AdvancedDyePopup( { var tmp = value; var minValue = ImGui.GetIO().KeyCtrl ? 0f : (float)Half.Epsilon; - if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), ImGuiSliderFlags.AlwaysClamp)) + if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), + ImGuiSliderFlags.AlwaysClamp)) return false; var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue); diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index f1ec440..be52b9c 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -6,6 +6,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Interop.Material; @@ -280,6 +282,24 @@ public readonly struct MaterialValueManager return true; } + public bool CheckExistence(MaterialValueIndex index) + { + if (_values.Count == 0) + return false; + + var key = index.Key; + var idx = Search(key); + if (idx >= 0) + return true; + + idx = ~idx; + if (idx >= _values.Count) + return false; + + var nextItem = MaterialValueIndex.FromKey(_values[idx].Key); + return nextItem.DrawObject == index.DrawObject && nextItem.SlotIndex == index.SlotIndex; + } + public bool RemoveValue(MaterialValueIndex index) => RemoveValue(index.Key); From 425c9471fbaa422d1185dbde7d6eff91133bdb09 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 23 Feb 2025 23:19:24 +0100 Subject: [PATCH 059/212] Highlight materials with advanced dyes even if inactive, allow removing inactive. --- Glamourer/Gui/Equipment/BonusDrawData.cs | 4 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 75 ++++++++++--------- .../Interop/Material/MaterialValueManager.cs | 24 ++++-- 4 files changed, 63 insertions(+), 44 deletions(-) diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index c14de2a..067c0c6 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -44,7 +44,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) CurrentApply = design.DoApplyBonusItem(slot), Locked = design.WriteProtected(), DisplayApplication = true, - HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), }; public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot) @@ -56,6 +56,6 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.BonusItem(slot), AllowRevert = true, - HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), }; } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index d13b9e4..77f4533 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -58,7 +58,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) CurrentApply = design.DoApplyEquip(slot), CurrentApplyStain = design.DoApplyStain(slot), Locked = design.WriteProtected(), - HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), DisplayApplication = true, }; @@ -71,7 +71,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.Item(slot), GameStains = state.BaseData.Stain(slot), - HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), AllowRevert = true, }; } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 4fa93c1..d6d7420 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -8,7 +8,6 @@ using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -102,11 +101,12 @@ public sealed unsafe class AdvancedDyePopup( private void DrawTabBar(ReadOnlySpan> textures, ReadOnlySpan> materials, ref bool firstAvailable) { - using var bar = ImRaii.TabBar("tabs"); + using var bar = ImUtf8.TabBar("tabs"u8); if (!bar) return; - var table = new ColorTable.Table(); + var table = new ColorTable.Table(); + var highLightColor = ColorId.AdvancedDyeActive.Value(); for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { var index = _drawIndex!.Value with { MaterialIndex = i }; @@ -125,17 +125,30 @@ public sealed unsafe class AdvancedDyePopup( if (available) firstAvailable = false; - using var tab = _label.TabItem(i, select); + var hasAdvancedDyes = _state.Materials.CheckExistenceMaterial(index); + using var c = ImRaii.PushColor(ImGuiCol.Text, highLightColor, hasAdvancedDyes); + using var tab = _label.TabItem(i, select); + c.Pop(); if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { using var enabled = ImRaii.Enabled(); var (path, gamePath) = ResourceName(index); + using var tt = ImUtf8.Tooltip(); + if (gamePath.Length == 0 || path.Length == 0) - ImGui.SetTooltip("This material does not exist."); + ImUtf8.Text("This material does not exist."u8); else if (!available) - ImGui.SetTooltip($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); + ImUtf8.Text($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); else - ImGui.SetTooltip($"{gamePath}\n{path}"); + ImUtf8.Text($"{gamePath}\n{path}"); + + if (hasAdvancedDyes && !available) + { + ImUtf8.Text("\nRight-Click to remove ineffective advanced dyes."u8); + if (ImGui.IsMouseClicked(ImGuiMouseButton.Right)) + for (byte row = 0; row < ColorTable.NumRows; ++row) + stateManager.ResetMaterialValue(_state, index with { RowIndex = row }, ApplySettings.Game); + } } if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) @@ -174,7 +187,7 @@ public sealed unsafe class AdvancedDyePopup( DrawTabBar(textures, materials, ref firstAvailable); if (firstAvailable) - ImGui.TextUnformatted("No Editable Materials available."); + ImUtf8.Text("No Editable Materials available."u8); } private void DrawWindow(ReadOnlySpan> textures, ReadOnlySpan> materials) @@ -256,25 +269,23 @@ public sealed unsafe class AdvancedDyePopup( { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight all affected colors on the character.", - false, true); + ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight all affected colors on the character."u8, buttonSize); if (ImGui.IsItemHovered()) preview.OnHover(materialIndex with { RowIndex = byte.MaxValue }, _actor.Index, table); ImGui.SameLine(); ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGui.TextUnformatted("All Color Row Pairs (1-16)"); + ImUtf8.Text("All Color Row Pairs (1-16)"u8); } var spacing = ImGui.GetStyle().ItemInnerSpacing.X; ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this table to your clipboard.", false, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this table to your clipboard."u8, buttonSize)) ColorRowClipboard.Table = table; ImGui.SameLine(0, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, - "Import an exported table from your clipboard onto this table.", !ColorRowClipboard.IsTableSet, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize, + !ColorRowClipboard.IsTableSet)) for (var idx = 0; idx < ColorTable.NumRows; ++idx) { var row = ColorRowClipboard.Table[idx]; @@ -288,15 +299,14 @@ public sealed unsafe class AdvancedDyePopup( } ImGui.SameLine(0, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this table to game state."u8, buttonSize, !_anyChanged)) for (byte i = 0; i < ColorTable.NumRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } private void DrawRow(ref ColorTableRow row, MaterialValueIndex index, in ColorTable.Table table) { - using var id = ImRaii.PushId(index.RowIndex); + using var id = ImUtf8.PushId(index.RowIndex); var changed = _state.Materials.TryGetValue(index, out var value); if (!changed) { @@ -319,8 +329,7 @@ public sealed unsafe class AdvancedDyePopup( } var buttonSize = new Vector2(ImGui.GetFrameHeight()); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.", - false, true); + ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight the affected colors on the character."u8, buttonSize); if (ImGui.IsItemHovered()) preview.OnHover(index, _actor.Index, table); @@ -330,28 +339,28 @@ public sealed unsafe class AdvancedDyePopup( { var rowIndex = index.RowIndex / 2 + 1; var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B'; - ImGui.TextUnformatted($"Row {rowIndex,2}{rowSuffix}"); + ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); - var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, - v => value.Model.Diffuse = v, "D"); + var applied = ImUtf8.ColorPicker("##diffuse"u8, "Change the diffuse value for this row."u8, value.Model.Diffuse, + v => value.Model.Diffuse = v, "D"u8); var spacing = ImGui.GetStyle().ItemInnerSpacing; ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, - v => value.Model.Specular = v, "S"); + applied |= ImUtf8.ColorPicker("##specular"u8, "Change the specular value for this row."u8, value.Model.Specular, + v => value.Model.Specular = v, "S"u8); ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, - v => value.Model.Emissive = v, "E"); + applied |= ImUtf8.ColorPicker("##emissive"u8, "Change the emissive value for this row."u8, value.Model.Emissive, + v => value.Model.Emissive = v, "E"u8); ImGui.SameLine(0, spacing.X); if (_mode is not ColorRow.Mode.Dawntrail) { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= DragGloss(ref value.Model.GlossStrength); - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImUtf8.HoverTooltip("Change the gloss strength for this row."u8); } else { @@ -363,7 +372,7 @@ public sealed unsafe class AdvancedDyePopup( { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= DragSpecularStrength(ref value.Model.SpecularStrength); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + ImUtf8.HoverTooltip("Change the specular strength for this row."u8); } else { @@ -371,19 +380,17 @@ public sealed unsafe class AdvancedDyePopup( } ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8, buttonSize)) ColorRowClipboard.Row = value.Model; ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, - "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, !ColorRowClipboard.IsSet)) { value.Model = ColorRowClipboard.Row; applied = true; } ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this row to game state.", !changed, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this row to game state."u8, buttonSize, !changed)) stateManager.ResetMaterialValue(_state, index, ApplySettings.Game); if (applied) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index be52b9c..520dacf 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -282,22 +282,34 @@ public readonly struct MaterialValueManager return true; } - public bool CheckExistence(MaterialValueIndex index) + public bool CheckExistenceSlot(MaterialValueIndex index) + { + var key = CheckExistence(index); + return key.Valid && key.DrawObject == index.DrawObject && key.SlotIndex == index.SlotIndex; + } + + public bool CheckExistenceMaterial(MaterialValueIndex index) + { + var key = CheckExistence(index); + return key.Valid && key.DrawObject == index.DrawObject && key.SlotIndex == index.SlotIndex && key.MaterialIndex == index.MaterialIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MaterialValueIndex CheckExistence(MaterialValueIndex index) { if (_values.Count == 0) - return false; + return MaterialValueIndex.Invalid; var key = index.Key; var idx = Search(key); if (idx >= 0) - return true; + return index; idx = ~idx; if (idx >= _values.Count) - return false; + return MaterialValueIndex.Invalid; - var nextItem = MaterialValueIndex.FromKey(_values[idx].Key); - return nextItem.DrawObject == index.DrawObject && nextItem.SlotIndex == index.SlotIndex; + return MaterialValueIndex.FromKey(_values[idx].Key); } public bool RemoveValue(MaterialValueIndex index) From 528aae7eeeeb49639e0470bd869ce1de205fcf73 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:24:51 +0100 Subject: [PATCH 060/212] Remove PalettePlusChecker and config options to disable Advanced Customization and Advanced Dyes. --- Glamourer/Api/StateApi.cs | 2 +- Glamourer/Configuration.cs | 2 - Glamourer/Designs/ApplicationRules.cs | 3 - Glamourer/Glamourer.cs | 2 - Glamourer/Gui/DesignQuickBar.cs | 6 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 6 -- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 3 - Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 64 +++++++------------ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 53 +++++++-------- Glamourer/Interop/Material/MaterialManager.cs | 3 - .../Interop/PalettePlus/PalettePlusChecker.cs | 49 -------------- Glamourer/State/StateApplier.cs | 26 +++----- Glamourer/State/StateListener.cs | 17 ++--- Glamourer/State/StateManager.cs | 2 +- 14 files changed, 66 insertions(+), 172 deletions(-) delete mode 100644 Glamourer/Interop/PalettePlus/PalettePlusChecker.cs diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index b2fdc9b..c27abb7 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -314,7 +314,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) return (GlamourerApiEc.InvalidKey, null); - return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config))); + return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.All)); } private (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4b59191..019bb2b 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -55,8 +55,6 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool ShowWindowWhenUiHidden { get; set; } = false; - public bool UseAdvancedParameters { get; set; } = true; - public bool UseAdvancedDyes { get; set; } = true; public bool KeepAdvancedDyesAttached { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public bool UseFloatForColors { get; set; } = true; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 703a3ae..0df4feb 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -19,9 +19,6 @@ public readonly struct ApplicationRules(ApplicationCollection application, bool public static ApplicationRules AllButParameters(ActorState state) => new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true); - public static ApplicationRules AllWithConfig(Configuration config) - => new(ApplicationCollection.All with { Parameters = config.UseAdvancedParameters ? All.Parameters : 0 }, config.UseAdvancedDyes); - public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) { var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index cf0b278..5d38e3a 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -69,8 +69,6 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Auto-Reload Gear: `** {config.AutoRedrawEquipOnChanges}\n"); sb.Append($"> **`Revert on Zone Change:`** {config.RevertManualChangesOnZoneChange}\n"); sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); - sb.Append($"> **`Advanced Customize: `** {config.UseAdvancedParameters}\n"); - sb.Append($"> **`Advanced Dye: `** {config.UseAdvancedDyes}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b125b37..cabd888 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -11,7 +11,6 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; @@ -304,9 +303,6 @@ public sealed class DesignQuickBar : Window, IDisposable private void DrawRevertAdvancedCustomization(Vector2 buttonSize) { - if (!_config.UseAdvancedParameters) - return; - if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) return; @@ -475,7 +471,7 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; } - if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index d6d7420..6d0bb70 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -38,9 +38,6 @@ public sealed unsafe class AdvancedDyePopup( private bool ShouldBeDrawn() { - if (!config.UseAdvancedDyes) - return false; - if (_drawIndex is not { Valid: true }) return false; @@ -58,9 +55,6 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index, uint color) { - if (!config.UseAdvancedDyes) - return; - ImGui.SameLine(); using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 9c8f3cf..e56266d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -239,9 +239,6 @@ public class ActorPanel private void DrawParameterHeader() { - if (!_config.UseAdvancedParameters) - return; - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 748afea..554e671 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -180,10 +180,7 @@ public class DesignPanel private void DrawCustomizeParameters() { - if (!_config.UseAdvancedParameters) - return; - - using var h = ImRaii.CollapsingHeader("Advanced Customizations"); + using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); if (!h) return; @@ -192,10 +189,7 @@ public class DesignPanel private void DrawMaterialValues() { - if (!_config.UseAdvancedDyes) - return; - - using var h = ImRaii.CollapsingHeader("Advanced Dyes"); + using var h = ImUtf8.CollapsingHeaderId("Advanced Dyes"u8); if (!h) return; @@ -204,7 +198,7 @@ public class DesignPanel private void DrawCustomizeApplication() { - using var id = ImRaii.PushId("Customizations"); + using var id = ImUtf8.PushId("Customizations"u8); var set = _selector.Selected!.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : @@ -219,55 +213,52 @@ public class DesignPanel } var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) + if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) + if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) { var apply = _selector.Selected!.DoApplyCustomize(index); - if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) + if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); } } private void DrawCrestApplication() { - using var id = ImRaii.PushId("Crests"); + using var id = ImUtf8.PushId("Crests"u8); var flags = (uint)_selector.Selected!.Application.Crest; var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); - if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) _manager.ChangeApplyCrest(_selector.Selected!, flag, apply); } } private void DrawApplicationRules() { - using var h = ImRaii.CollapsingHeader("Application Rules"); + using var h = ImUtf8.CollapsingHeaderId("Application Rules"u8); if (!h) return; using var disabled = ImRaii.Disabled(_selector.Selected!.WriteProtected()); - using (var _ = ImRaii.Group()) + using (var _ = ImUtf8.Group()) { DrawCustomizeApplication(); ImUtf8.IconDummy(); DrawCrestApplication(); ImUtf8.IconDummy(); - if (_config.UseAdvancedParameters) - { - DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); - } + DrawMetaApplication(); + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); @@ -276,20 +267,20 @@ public class DesignPanel void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { var flags = (uint)(allFlags & _selector.Selected!.Application.Equip); - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot); - if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) _manager.ChangeApplyStains(_selector.Selected!, slot, apply); } else foreach (var slot in slots) { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot); - if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) _manager.ChangeApplyItem(_selector.Selected!, slot, apply); } } @@ -311,16 +302,7 @@ public class DesignPanel EquipSlotExtensions.FullSlots); ImUtf8.IconDummy(); - if (_config.UseAdvancedParameters) - { - DrawParameterApplication(); - } - else - { - DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); - } + DrawParameterApplication(); } } @@ -334,7 +316,7 @@ public class DesignPanel private void DrawMetaApplication() { - using var id = ImRaii.PushId("Meta"); + using var id = ImUtf8.PushId("Meta"); const uint all = (uint)MetaExtensions.All; var flags = (uint)_selector.Selected!.Application.Meta; var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); @@ -342,7 +324,7 @@ public class DesignPanel foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) { var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); - if (ImGui.Checkbox(label, ref apply) || bigChange) + if (ImUtf8.Checkbox(label, ref apply) || bigChange) _manager.ChangeApplyMeta(_selector.Selected!, index, apply); } } @@ -368,20 +350,20 @@ public class DesignPanel private void DrawParameterApplication() { - using var id = ImRaii.PushId("Parameter"); + using var id = ImUtf8.PushId("Parameter"); var flags = (uint)_selector.Selected!.Application.Parameters; var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); - if (ImGui.Checkbox($"Apply {flag.ToName()}", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {flag.ToName()}", ref apply) || bigChange) _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); } } public void Draw() { - using var group = ImRaii.Group(); + using var group = ImUtf8.Group(); if (_selector.SelectedPaths.Count > 1) { _multiDesignPanel.Draw(); @@ -419,10 +401,12 @@ public class DesignPanel using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); if (!table || _selector.Selected == null) return; + ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); if (_selector.Selected == null) return; + ImGui.Dummy(Vector2.Zero); DrawButtonRow(); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 4ee261b..fd4f3f5 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -24,7 +24,6 @@ public class SettingsTab( IKeyState keys, DesignColorUi designColorUi, PaletteImport paletteImport, - PalettePlusChecker paletteChecker, CollectionOverrideDrawer overrides, CodeDrawer codeDrawer, Glamourer glamourer, @@ -93,20 +92,15 @@ public class SettingsTab( Checkbox("Revert Manual Changes on Zone Change"u8, "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options"u8, - "Enable the display and editing of advanced customization options like arbitrary colors."u8, - config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); - Checkbox("Enable Advanced Dye Options"u8, - "Enable the display and editing of advanced dyes (color sets) for all equipment"u8, - config.UseAdvancedDyes, v => config.UseAdvancedDyes = v); Checkbox("Always Apply Associated Mods"u8, "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"u8 + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault."u8, config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); Checkbox("Use Temporary Mod Settings"u8, - "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, + "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, + config.UseTemporarySettings, v => config.UseTemporarySettings = v); ImGui.NewLine(); } @@ -152,7 +146,7 @@ public class SettingsTab( ImGui.Dummy(Vector2.Zero); Checkbox("Enable Game Context Menus"u8, "Whether to show a Try On via Glamourer button on context menus for equippable items."u8, - config.EnableGameContextMenu, v => + config.EnableGameContextMenu, v => { config.EnableGameContextMenu = v; if (v) @@ -161,12 +155,13 @@ public class SettingsTab( contextMenuService.Disable(); }); Checkbox("Show Window when UI is Hidden"u8, "Whether to show Glamourer windows even when the games UI is hidden."u8, - config.ShowWindowWhenUiHidden, v => + config.ShowWindowWhenUiHidden, v => { config.ShowWindowWhenUiHidden = v; uiBuilder.DisableUserUiHide = v; }); - Checkbox("Hide Window in Cutscenes"u8, "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, + Checkbox("Hide Window in Cutscenes"u8, + "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, config.HideWindowInCutscene, v => { @@ -177,13 +172,13 @@ public class SettingsTab( config.Ephemeral.LockMainWindow, v => config.Ephemeral.LockMainWindow = v); Checkbox("Open Main Window at Game Start"u8, "Whether the main Glamourer window should be open or closed after launching the game."u8, - config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); + config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8, - config.SmallEquip, v => config.SmallEquip = v); + config.SmallEquip, v => config.SmallEquip = v); DrawHeightUnitSettings(); Checkbox("Show Application Checkboxes"u8, "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8, @@ -211,23 +206,22 @@ public class SettingsTab( Checkbox("Show Unobtained Item Warnings"u8, "Show information whether you have unlocked all items and customizations in your automated design or not."u8, config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); - if (config.UseAdvancedParameters) + Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, + config.ShowColorConfig, v => config.ShowColorConfig = v); + Checkbox("Show Palette+ Import Button"u8, + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, + config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); + using (ImRaii.PushId(1)) { - Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, - config.ShowColorConfig, v => config.ShowColorConfig = v); - Checkbox("Show Palette+ Import Button"u8, - "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, - config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); - using var id = ImRaii.PushId(1); PaletteImportButton(); } - if (config.UseAdvancedDyes) - Checkbox("Keep Advanced Dye Window Attached"u8, - "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, - config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); + Checkbox("Keep Advanced Dye Window Attached"u8, + "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, + config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); - Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, config.DebugMode, + Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, + config.DebugMode, v => config.DebugMode = v); ImGui.NewLine(); } @@ -235,8 +229,7 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 8 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); @@ -253,11 +246,11 @@ public class SettingsTab( ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), ("Revert Equip", true, QdbButtons.RevertEquip), ("Revert Customize", true, QdbButtons.RevertCustomize), - ("Revert Advanced", showAdvanced, QdbButtons.RevertAdvanced), + ("Revert Advanced", true, QdbButtons.RevertAdvanced), ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), ]; - for(var i = 0; i < columns.Length; ++i) + for (var i = 0; i < columns.Length; ++i) { if (!columns[i].Item2) continue; @@ -291,7 +284,7 @@ public class SettingsTab( private void PaletteImportButton() { - if (!config.UseAdvancedParameters || !config.ShowPalettePlusImport) + if (!config.ShowPalettePlusImport) return; ImGui.SameLine(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index f3c1875..81373c5 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -41,9 +41,6 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainIds stain, ref nint ret) { - if (!_config.UseAdvancedDyes) - return; - var actor = _penumbra.GameObjectFromDrawObject(characterBase); var validType = FindType(characterBase, actor, out var type); var (slotId, materialId) = FindMaterial(characterBase, material); diff --git a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs deleted file mode 100644 index a5a5ed9..0000000 --- a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin; -using OtterGui.Services; -using Notification = OtterGui.Classes.Notification; - -namespace Glamourer.Interop.PalettePlus; - -public sealed class PalettePlusChecker : IRequiredService, IDisposable -{ - private readonly Timer _paletteTimer; - private readonly Configuration _config; - private readonly IDalamudPluginInterface _pluginInterface; - - public PalettePlusChecker(Configuration config, IDalamudPluginInterface pluginInterface) - { - _config = config; - _pluginInterface = pluginInterface; - _paletteTimer = new Timer(_ => PalettePlusCheck(), null, TimeSpan.FromSeconds(30), Timeout.InfiniteTimeSpan); - } - - public void Dispose() - => _paletteTimer.Dispose(); - - public void SetAdvancedParameters(bool value) - { - _config.UseAdvancedParameters = value; - PalettePlusCheck(); - } - - private void PalettePlusCheck() - { - if (!_config.UseAdvancedParameters) - return; - - try - { - var subscriber = _pluginInterface.GetIpcSubscriber("PalettePlus.ApiVersion"); - subscriber.InvokeFunc(); - Glamourer.Messager.AddMessage(new Notification( - "You currently have Palette+ installed. This conflicts with Glamourers advanced options and will cause invalid state.\n\n" - + "Please uninstall Palette+ and restart your game. Palette+ is deprecated and no longer supported by Mare Synchronos.", - NotificationType.Warning, 10000)); - } - catch - { - // ignored - } - } -} diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 2e086d6..be85580 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -25,7 +25,6 @@ public class StateApplier( MetaService _metaService, ObjectManager _objects, CrestService _crests, - Configuration _config, DirectXService _directX) { /// Simply force a redraw regardless of conditions. @@ -291,30 +290,26 @@ public class StateApplier( } /// Change the customize parameters on models. Can change multiple at once. - public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values, bool force) + public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) { - if (!force && !_config.UseAdvancedParameters || flags == 0) + if (flags == 0) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) actor.Model.ApplyParameterData(flags, values); } - /// + /// public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply) { var data = GetData(state); if (apply) - ChangeParameters(data, flags, state.ModelData.Parameters, state.IsLocked); + ChangeParameters(data, flags, state.ModelData.Parameters); return data; } - public unsafe void ChangeMaterialValue(ActorState state, ActorData data, MaterialValueIndex changedIndex, ColorRow? changedValue, - bool force) + public unsafe void ChangeMaterialValue(ActorState state, ActorData data, MaterialValueIndex changedIndex, ColorRow? changedValue) { - if (!force && !_config.UseAdvancedDyes) - return; - foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) { if (!changedIndex.TryGetTexture(actor, out var texture)) @@ -341,16 +336,13 @@ public class StateApplier( { var data = GetData(state); if (apply) - ChangeMaterialValue(state, data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); + ChangeMaterialValue(state, data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null); return data; } - public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) + public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials) { - if (!force && !_config.UseAdvancedDyes) - return; - var groupedMaterialValues = materials.Values.Select(p => (MaterialValueIndex.FromKey(p.Key), p.Value)) .GroupBy(p => (p.Item1.DrawObject, p.Item1.SlotIndex, p.Item1.MaterialIndex)); @@ -410,8 +402,8 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); ChangeCrests(actors, state.ModelData.CrestVisibility); - ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); - ChangeMaterialValues(actors, state.Materials, state.IsLocked); + ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); + ChangeMaterialValues(actors, state.Materials); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3a6d6ef..88ec0b5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -41,7 +41,7 @@ public class StateListener : IDisposable private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateFinalized _stateFinalized; + private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -88,7 +88,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; - _stateFinalized = stateFinalized; + _stateFinalized = stateFinalized; Subscribe(); } @@ -225,7 +225,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); @@ -889,7 +889,7 @@ public class StateListener : IDisposable case StateSource.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); - else if (_config.UseAdvancedParameters) + else model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcManual: @@ -900,8 +900,7 @@ public class StateListener : IDisposable break; case StateSource.Fixed: state.BaseData.Parameters.Set(flag, newValue); - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcFixed: state.BaseData.Parameters.Set(flag, newValue); @@ -910,14 +909,12 @@ public class StateListener : IDisposable case StateSource.Pending: state.BaseData.Parameters.Set(flag, newValue); state.Sources[flag] = StateSource.Manual; - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcPending: state.BaseData.Parameters.Set(flag, newValue); state.Sources[flag] = StateSource.IpcManual; - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 9b71586..2dda310 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -297,7 +297,7 @@ public sealed class StateManager( { actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); foreach (var (idx, mat) in state.Materials.Values) - Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game, true); + Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game); } state.Materials.Clear(); From 94f6e870e6c96e21bc06221dc8249aa94311e261 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:33:41 +0100 Subject: [PATCH 061/212] Update Submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index 06422a8..c347d29 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 06422a893348a18a013e6dbc558370db8d21a446 +Subproject commit c347d29d980b0191d1d071170cf2ec229e3efdcf diff --git a/Penumbra.GameData b/Penumbra.GameData index 33fea10..a21c146 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 33fea10e18ec9f8a5b309890de557fcb25780086 +Subproject commit a21c146790b370bd58b0f752385ae153f7e769c0 From 7015737d887a9918f0d5f5175d98df9e6a81ee73 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:38:58 +0100 Subject: [PATCH 062/212] Meh. --- Glamourer/GameData/NpcCustomizeSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 3cc19cd..725f80f 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -330,9 +330,9 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Check decal files for existence. private static void CheckFacepaintFiles(IDataManager data, BitArray facepaints) { - for (var i = 0; i < 128; ++i) + for (byte i = 0; i < 128; ++i) { - var path = GamePaths.Character.Tex.DecalPath("face", (PrimaryId)i); + var path = GamePaths.Tex.FaceDecal(i); if (data.FileExists(path)) facepaints[i] = true; } From 311207977623c800449299fc6f243a1f246e4a19 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 2 Mar 2025 12:40:58 +0000 Subject: [PATCH 063/212] [CI] Updating repo.json for testing_1.3.6.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 31b5654..fe2a7c2 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.3", + "TestingAssemblyVersion": "1.3.6.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c96f009fb431f0093015e8c9a0bec42df3f466f6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 16:02:02 +0100 Subject: [PATCH 064/212] Slightly speed up QDB drawing. --- Glamourer/Gui/DesignQuickBar.cs | 130 +++++++++++++++++++------------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index cabd888..d2dba01 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -48,6 +48,7 @@ public sealed class DesignQuickBar : Window, IDisposable private readonly ImRaii.Color _windowColor = new(); private DateTime _keyboardToggle = DateTime.UnixEpoch; private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) @@ -107,7 +108,7 @@ public sealed class DesignQuickBar : Window, IDisposable private void Draw(float width) { - using var group = ImRaii.Group(); + using var group = ImUtf8.Group(); var spacing = ImGui.GetStyle().ItemInnerSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -149,33 +150,38 @@ public sealed class DesignQuickBar : Window, IDisposable { var design = _designCombo.Design as Design; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); + if (design == null) { - tooltip = "No design selected."; + _tooltipBuilder.Append("No design selected."); } else { if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to yourself."; + _tooltipBuilder.Append("Left-Click: Apply ") + .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) + .Append(" to yourself."); } if (_targetIdentifier.IsValid && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to {_targetIdentifier}."; + _tooltipBuilder.Append("Right-Click: Apply ") + .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) + .Append(" to {_targetIdentifier}."); } if (available == 0) - tooltip = "Neither player character nor target available."; + _tooltipBuilder.Append("Neither player character nor target available."); } - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, available); ImGui.SameLine(); if (!clicked) return; @@ -197,25 +203,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false }) { available |= 1; - tooltip = "Left-Click: Revert the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false }) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetState(state!, StateSource.Manual, isFinal: true); @@ -230,26 +239,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the player character to their automation state."; + _tooltipBuilder.Append("Left-Click: Revert the player character to their automation state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; + _tooltipBuilder.Append("Right-Click: Revert ") + .Append(_targetIdentifier) + .Append(" to their automation state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -270,26 +281,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Reapply the player character's current automation on top of their current state."; + _tooltipBuilder.Append("Left-Click: Reapply the player character's current automation on top of their current state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Reapply {_targetIdentifier}'s current automation on top of their current state."; + _tooltipBuilder.Append("Right-Click: Reapply ") + .Append(_targetIdentifier) + .Append("'s current automation on top of their current state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -307,26 +320,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the advanced customizations and dyes of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert the advanced customizations and dyes of ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetAdvancedState(state!, StateSource.Manual); @@ -338,26 +353,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the customizations of the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the customizations of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the customizations of {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert the customizations of ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetCustomize(state!, StateSource.Manual); @@ -369,26 +386,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the equipment of the player character to its game state."; + _tooltipBuilder.Append("Left-Click: Revert the equipment of the player character to its game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the equipment of {_targetIdentifier} to its game state."; + _tooltipBuilder.Append("Right-Click: Revert the equipment of ") + .Append(_targetIdentifier) + .Append(" to its game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetEquip(state!, StateSource.Manual); @@ -400,26 +419,30 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_playerIdentifier}."; + _tooltipBuilder.Append("Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + .Append(_playerIdentifier) + .Append('.'); } if (_targetIdentifier.IsValid && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_targetIdentifier}."; + _tooltipBuilder.Append("Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + .Append(_targetIdentifier) + .Append('.'); } if (available == 0) - tooltip = "Neither player character nor target are available to identify their collections."; + _tooltipBuilder.Append("Neither player character nor target are available to identify their collections."); - var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); + var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, available); ImGui.SameLine(); if (clicked) { @@ -428,10 +451,11 @@ public sealed class DesignQuickBar : Window, IDisposable } } - private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, - int available) + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, int available) { - ImUtf8.IconButton(icon, tooltip, buttonSize, available == 0); + var enumerator = _tooltipBuilder.GetChunks(); + var span = enumerator.MoveNext() ? enumerator.Current.Span : []; + ImUtf8.IconButton(icon, span, buttonSize, available == 0); if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) return (true, _playerIdentifier, _playerData, _playerState); if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) From 6e685b96d11da86fbb19037da075763183f41da3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 16:31:09 +0100 Subject: [PATCH 065/212] Make all panels configurable. --- Glamourer/Configuration.cs | 3 + Glamourer/DesignPanelFlag.cs | 96 +++++++++++++++++++ Glamourer/Glamourer.cs | 1 + Glamourer/Gui/Materials/AdvancedDyePopup.cs | 3 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 15 ++- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 9 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 14 ++- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 14 ++- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 7 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 24 +++-- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 15 +++ 11 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 Glamourer/DesignPanelFlag.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 019bb2b..ef51544 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -66,6 +66,9 @@ public class Configuration : IPluginConfiguration, ISavable public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + public DesignPanelFlag HideDesignPanel { get; set; } = 0; + public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0; + public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/DesignPanelFlag.cs new file mode 100644 index 0000000..db84173 --- /dev/null +++ b/Glamourer/DesignPanelFlag.cs @@ -0,0 +1,96 @@ +using Glamourer.Designs; +using ImGuiNET; +using OtterGui.Text; +using OtterGui.Text.EndObjects; + +namespace Glamourer; + +[Flags] +public enum DesignPanelFlag : uint +{ + Customization = 0x0001, + Equipment = 0x0002, + AdvancedCustomizations = 0x0004, + AdvancedDyes = 0x0008, + AppearanceDetails = 0x0010, + DesignDetails = 0x0020, + ModAssociations = 0x0040, + DesignLinks = 0x0080, + ApplicationRules = 0x0100, + DebugData = 0x0200, +} + +public static class DesignPanelFlagExtensions +{ + public static ReadOnlySpan ToName(this DesignPanelFlag flag) + => flag switch + { + DesignPanelFlag.Customization => "Customization"u8, + DesignPanelFlag.Equipment => "Equipment"u8, + DesignPanelFlag.AdvancedCustomizations => "Advanced Customization"u8, + DesignPanelFlag.AdvancedDyes => "Advanced Dyes"u8, + DesignPanelFlag.DesignDetails => "Design Details"u8, + DesignPanelFlag.ApplicationRules => "Application Rules"u8, + DesignPanelFlag.ModAssociations => "Mod Associations"u8, + DesignPanelFlag.DesignLinks => "Design Links"u8, + DesignPanelFlag.DebugData => "Debug Data"u8, + DesignPanelFlag.AppearanceDetails => "Appearance Details"u8, + _ => ""u8, + }; + + public static CollapsingHeader Header(this DesignPanelFlag flag, Configuration config) + { + if (config.HideDesignPanel.HasFlag(flag)) + return new CollapsingHeader() + { + Disposed = true, + }; + + var expand = config.AutoExpandDesignPanel.HasFlag(flag); + return ImUtf8.CollapsingHeaderId(flag.ToName(), expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); + } + + public static void DrawTable(ReadOnlySpan label, DesignPanelFlag hidden, DesignPanelFlag expanded, Action setterHide, + Action setterExpand) + { + var checkBoxWidth = Math.Max(ImGui.GetFrameHeight(), ImUtf8.CalcTextSize("Expand"u8).X); + var textWidth = ImUtf8.CalcTextSize(DesignPanelFlag.AdvancedCustomizations.ToName()).X; + var tableSize = 2 * (textWidth + 2 * checkBoxWidth) + 10 * ImGui.GetStyle().CellPadding.X + 2 * ImGui.GetStyle().WindowPadding.X + 2 * ImGui.GetStyle().FrameBorderSize; + using var table = ImUtf8.Table(label, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders, new Vector2(tableSize, 6 * ImGui.GetFrameHeight())); + if (!table) + return; + + var headerColor = ImGui.GetColorU32(ImGuiCol.TableHeaderBg); + var checkBoxOffset = (checkBoxWidth - ImGui.GetFrameHeight()) / 2; + ImUtf8.TableSetupColumn("Panel##1"u8, ImGuiTableColumnFlags.WidthFixed, textWidth); + ImUtf8.TableSetupColumn("Show##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Expand##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Panel##2"u8, ImGuiTableColumnFlags.WidthFixed, textWidth); + ImUtf8.TableSetupColumn("Show##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Expand##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + + ImGui.TableHeadersRow(); + foreach (var panel in Enum.GetValues()) + { + using var id = ImUtf8.PushId((int)panel); + ImGui.TableNextColumn(); + ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, headerColor); + ImUtf8.TextFrameAligned(panel.ToName()); + var isShown = !hidden.HasFlag(panel); + var isExpanded = expanded.HasFlag(panel); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset); + if (ImUtf8.Checkbox("##show"u8, ref isShown)) + setterHide.Invoke(isShown ? hidden & ~panel : hidden | panel); + ImUtf8.HoverTooltip( + "Show this panel and associated functionality in all relevant tabs.\n\nToggling this off does NOT disable any functionality, just the display of it, so hide panels at your own risk."u8); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset); + if (ImUtf8.Checkbox("##expand"u8, ref isExpanded)) + setterExpand.Invoke(isExpanded ? expanded | panel : expanded & ~panel); + ImUtf8.HoverTooltip("Expand this panel by default in all relevant tabs."u8); + } + } +} diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 5d38e3a..9191c4f 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -71,6 +71,7 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); + sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n"); sb.Append($"> **`Smaller Equip Display:`** {config.SmallEquip}\n"); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 6d0bb70..21e5ef9 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -55,6 +55,9 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index, uint color) { + if (config.HideDesignPanel.HasFlag(DesignPanelFlag.AdvancedDyes)) + return; + ImGui.SameLine(); using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e56266d..0071b1f 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -59,7 +59,7 @@ public class ActorPanel ICondition conditions, DictModelChara modelChara, CustomizeParameterDrawer parameterDrawer, - AdvancedDyePopup advancedDyes, + AdvancedDyePopup advancedDyes, EditorHistory editorHistory) { _selector = selector; @@ -157,6 +157,7 @@ public class ActorPanel using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); if (!table || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; + ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); ImGui.Dummy(Vector2.Zero); @@ -191,10 +192,14 @@ public class ActorPanel private void DrawCustomizationsHeader() { + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + var header = _state!.ModelData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_state.ModelData.ModelId})###Customization"; - using var h = ImUtf8.CollapsingHeaderId(header); + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -207,7 +212,7 @@ public class ActorPanel private void DrawEquipmentHeader() { - using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -239,7 +244,7 @@ public class ActorPanel private void DrawParameterHeader() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); + using var h = DesignPanelFlag.AdvancedCustomizations.Header(_config); if (!h) return; @@ -251,7 +256,7 @@ public class ActorPanel if (!_config.DebugMode) return; - using var h = ImUtf8.CollapsingHeaderId("Debug Data"u8); + using var h = DesignPanelFlag.DebugData.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 1469deb..cbf7acd 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -14,6 +14,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignDetailTab { private readonly SaveService _saveService; + private readonly Configuration _config; private readonly DesignFileSystemSelector _selector; private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager; @@ -30,19 +31,20 @@ public class DesignDetailTab private DesignFileSystem.Leaf? _changeLeaf; public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, - DesignColors colors) + DesignColors colors, Configuration config) { _saveService = saveService; _selector = selector; _manager = manager; _fileSystem = fileSystem; _colors = colors; + _config = config; _colorCombo = new DesignColorCombo(_colors, false); } public void Draw() { - using var h = ImUtf8.CollapsingHeaderId("Design Details"u8); + using var h = DesignPanelFlag.DesignDetails.Header(_config); if (!h) return; @@ -159,7 +161,8 @@ public class DesignDetailTab ImGui.TableNextColumn(); if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) _manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); - ImUtf8.HoverTooltip("Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); + ImUtf8.HoverTooltip( + "Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); ImUtf8.DrawFrameColumn("Color"u8); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 5a8c41c..b81ffdf 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -10,7 +10,12 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo, DesignColors _colorManager) : IUiService +public class DesignLinkDrawer( + DesignLinkManager _linkManager, + DesignFileSystemSelector _selector, + LinkDesignCombo _combo, + DesignColors _colorManager, + Configuration config) : IUiService { private int _dragDropIndex = -1; private LinkOrder _dragDropOrder = LinkOrder.None; @@ -19,12 +24,15 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe public void Draw() { - using var header = ImRaii.CollapsingHeader("Design Links"); + using var h = DesignPanelFlag.DesignLinks.Header(config); + if (h.Disposed) + return; + ImGuiUtil.HoverTooltip( "Design links are links to other designs that will be applied to characters or during automation according to the rules set.\n" + "They apply from top to bottom just like automated design sets, so anything set by an earlier design will not not be set again by later designs, and order is important.\n" + "If a linked design links to other designs, they will also be applied, so circular links are prohibited. "); - if (!header) + if (!h) return; DrawList(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 554e671..f576223 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -101,7 +101,7 @@ public class DesignPanel private void DrawEquipment() { - using var h = ImRaii.CollapsingHeader("Equipment"); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -156,10 +156,14 @@ public class DesignPanel private void DrawCustomize() { + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + var header = _selector.Selected!.DesignData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_selector.Selected!.DesignData.ModelId})###Customization"; - using var h = ImRaii.CollapsingHeader(header); + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -180,7 +184,7 @@ public class DesignPanel private void DrawCustomizeParameters() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); + using var h = DesignPanelFlag.AdvancedCustomizations.Header(_config); if (!h) return; @@ -189,7 +193,7 @@ public class DesignPanel private void DrawMaterialValues() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Dyes"u8); + using var h = DesignPanelFlag.AdvancedDyes.Header(_config); if (!h) return; @@ -244,7 +248,7 @@ public class DesignPanel private void DrawApplicationRules() { - using var h = ImUtf8.CollapsingHeaderId("Application Rules"u8); + using var h = DesignPanelFlag.ApplicationRules.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 5856fcc..f172735 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -21,7 +21,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void Draw() { - using var h = ImRaii.CollapsingHeader("Mod Associations"); + using var h = DesignPanelFlag.ModAssociations.Header(config); + if (h.Disposed) + return; + ImGuiUtil.HoverTooltip( "This tab can store information about specific mods associated with this design.\n\n" + "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n" @@ -98,7 +101,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch); if (config.UseTemporarySettings) ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); - ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); + ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X); ImUtf8.TableSetupColumn("Priority"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X); ImUtf8.TableSetupColumn("##Options"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index aeb96f6..1151e86 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -19,6 +19,7 @@ namespace Glamourer.Gui.Tabs.NpcTab; public class NpcPanel { + private readonly Configuration _config; private readonly DesignColorCombo _colorCombo; private string _newName = string.Empty; private DesignBase? _newDesign; @@ -42,7 +43,8 @@ public class NpcPanel DesignManager designManager, StateManager state, ObjectManager objects, - DesignColors colors) + DesignColors colors, + Configuration config) { _selector = selector; _favorites = favorites; @@ -53,6 +55,7 @@ public class NpcPanel _state = state; _objects = objects; _colors = colors; + _config = config; _colorCombo = new DesignColorCombo(colors, true); _leftButtons = [ @@ -139,9 +142,14 @@ public class NpcPanel private void DrawCustomization() { - using var h = _selector.Selection.ModelId == 0 - ? ImUtf8.CollapsingHeaderId("Customization"u8) - : ImUtf8.CollapsingHeaderId($"Customization (Model Id #{_selector.Selection.ModelId})###Customization"); + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + + var header = _selector.Selection.ModelId == 0 + ? "Customization" + : $"Customization (Model Id #{_selector.Selection.ModelId})###Customization"; + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -151,7 +159,7 @@ public class NpcPanel private void DrawEquipment() { - using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -190,7 +198,9 @@ public class NpcPanel private void DrawApplyToSelf() { var (id, data) = _objects.PlayerData; - if (!ImUtf8.ButtonEx("Apply to Yourself"u8, "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, Vector2.Zero, !data.Valid)) + if (!ImUtf8.ButtonEx("Apply to Yourself"u8, + "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, + Vector2.Zero, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) @@ -221,7 +231,7 @@ public class NpcPanel private void DrawAppearanceInfo() { - using var h = ImUtf8.CollapsingHeaderId("Appearance Details"u8); + using var h = DesignPanelFlag.AppearanceDetails.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index fd4f3f5..d6d2c15 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -136,6 +136,7 @@ public class SettingsTab( 100 * ImGuiHelpers.GlobalScale, config.ToggleQuickDesignBar, v => config.ToggleQuickDesignBar = v, _validKeys)) config.Save(); + Checkbox("Show Quick Design Bar in Main Window"u8, "Show the quick design bar in the tab selection part of the main window, too."u8, config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); @@ -193,6 +194,20 @@ public class SettingsTab( v => config.OpenFoldersByDefault = v); DrawFolderSortType(); + ImGui.NewLine(); + ImUtf8.Text("Show the following panels in their respective tabs:"u8); + ImGui.Dummy(Vector2.Zero); + DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => + { + config.HideDesignPanel = v; + config.Save(); + }, v => + { + config.AutoExpandDesignPanel = v; + config.Save(); + }); + + ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); From b9e4c144c20f560d7ddc0f0aafb1124bdd3faacf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 17:55:38 +0100 Subject: [PATCH 066/212] Add some buttons to set design application rules to some presets. --- Glamourer/Designs/DesignManager.cs | 34 +++++++++ Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 84 +++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f931489..f9429ad 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -3,6 +3,7 @@ using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -448,6 +449,39 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value)); } + /// Change multiple application values at once. + public void ChangeApplyMulti(Design design, bool? equipment, bool? customization, bool? bonus, bool? parameters, bool? meta, bool? stains, + bool? materials, bool? crest) + { + if (equipment is { } e) + foreach (var f in EquipSlotExtensions.FullSlots) + ChangeApplyItem(design, f, e); + if (stains is { } s) + foreach (var f in EquipSlotExtensions.FullSlots) + ChangeApplyStains(design, f, s); + if (customization is { } c) + foreach (var f in CustomizationExtensions.All.Where(design.CustomizeSet.IsAvailable).Prepend(CustomizeIndex.Clan) + .Prepend(CustomizeIndex.Gender)) + ChangeApplyCustomize(design, f, c); + if (bonus is { } b) + foreach (var f in BonusExtensions.AllFlags) + ChangeApplyBonusItem(design, f, b); + if (meta is { } m) + foreach (var f in MetaExtensions.AllRelevant) + ChangeApplyMeta(design, f, m); + if (crest is { } cr) + foreach (var f in CrestExtensions.AllRelevantSet) + ChangeApplyCrest(design, f, cr); + + if (parameters is { } p) + foreach (var f in CustomizeParameterExtensions.AllFlags) + ChangeApplyParameter(design, f, p); + + if (materials is { } ma) + foreach (var (key, _) in design.GetMaterialData()) + ChangeApplyMaterialValue(design, MaterialValueIndex.FromKey(key), ma); + } + #endregion public void UndoDesignChange(Design design) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 1b5e65a..5b3af31 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -44,7 +44,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void DrawName(MaterialValueIndex index) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); ImUtf8.TextFramed(index.ToString(), 0, new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0), borderColor: ImGui.GetColorU32(ImGuiCol.Text)); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f576223..caea8fe 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -254,6 +254,8 @@ public class DesignPanel using var disabled = ImRaii.Disabled(_selector.Selected!.WriteProtected()); + DrawAllButtons(); + using (var _ = ImUtf8.Group()) { DrawCustomizeApplication(); @@ -310,6 +312,88 @@ public class DesignPanel } } + private void DrawAllButtons() + { + var enabled = _config.DeleteDesignModifier.IsActive(); + bool? equip = null; + bool? customize = null; + var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + if (ImUtf8.ButtonEx("Disable Everything"u8, + "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + !enabled)) + { + equip = false; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Enable Everything"u8, + "Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + !enabled)) + { + equip = true; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Equipment Only"u8, + "Enable application of anything related to gear, disable anything that is not related to gear."u8, size, + !enabled)) + { + equip = true; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Customization Only"u8, + "Enable application of anything related to customization, disable anything that is not related to customization."u8, size, + !enabled)) + { + equip = false; + customize = true; + } + + if (ImUtf8.ButtonEx("Default Application"u8, + "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, + size, + !enabled)) + { + _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); + } + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, + size, + !enabled)) + _manager.ChangeApplyMulti(_selector.Selected!, null, null, null, false, null, null, false, null); + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + if (equip is null && customize is null) + return; + + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize, null, equip, equip, equip); + if (equip.HasValue) + { + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); + } + + if (customize.HasValue) + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, customize.Value); + } + private static readonly IReadOnlyList MetaLabels = [ "Apply Wetness", From 3fd6108fa1749c4e65958b8b3a4d2f5b4f437c9b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 18:12:42 +0100 Subject: [PATCH 067/212] Add buttons to remove, enable and disable multiple advanced dyes at once in a design. --- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 48 +++++++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f9429ad..ef01e98 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -478,7 +478,7 @@ public sealed class DesignManager : DesignEditor ChangeApplyParameter(design, f, p); if (materials is { } ma) - foreach (var (key, _) in design.GetMaterialData()) + foreach (var (key, _) in design.GetMaterialData().ToArray()) ChangeApplyMaterialValue(design, MaterialValueIndex.FromKey(key), ma); } diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 5b3af31..0c4433d 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -35,6 +35,10 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) + (GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + 6 * _spacing + ImUtf8.CalcTextSize("Revert"u8).X; + DrawMultiButtons(design); + ImUtf8.Dummy(0); + ImGui.Separator(); + ImUtf8.Dummy(0); if (available > 1.95 * colorWidth) DrawSingleRow(design); else @@ -42,6 +46,39 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) DrawNew(design); } + private void DrawMultiButtons(Design design) + { + var any = design.Materials.Count > 0; + var disabled = !_config.DeleteDesignModifier.IsActive(); + var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + if (ImUtf8.ButtonEx("Enable All Advanced Dyes"u8, + any + ? "Enable the application of all contained advanced dyes without deleting them."u8 + : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + _designManager.ChangeApplyMulti(design, null, null, null, null, null, null, true, null); + ; + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to enable."); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable All Advanced Dyes"u8, + any + ? "Disable the application of all contained advanced dyes without deleting them."u8 + : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + _designManager.ChangeApplyMulti(design, null, null, null, null, null, null, false, null); + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to disable."); + + if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, any ? ""u8 : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + while (design.Materials.Count > 0) + _designManager.ChangeMaterialValue(design, MaterialValueIndex.FromKey(design.Materials[0].Item1), null); + + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to delete."); + } + private void DrawName(MaterialValueIndex index) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); @@ -139,7 +176,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8); } - public sealed class MaterialSlotCombo; + public sealed class MaterialSlotCombo; public void DrawNew(Design design) { @@ -165,22 +202,27 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) { ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Material AA"u8).X); var format = $"Material {(char)('A' + _newMaterialIdx)}"; - if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f)) + if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f, + ImGuiSliderFlags.NoInput)) { _newMaterialIdx = Math.Clamp(_newMaterialIdx, 0, MaterialService.MaterialsPerModel - 1); _newKey = _newKey with { MaterialIndex = (byte)_newMaterialIdx }; } + + ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8); } private void DrawRowIdxDrag() { ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Row 0000"u8).X); var format = $"Row {_newRowIdx / 2 + 1}{(char)(_newRowIdx % 2 + 'A')}"; - if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f)) + if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f, ImGuiSliderFlags.NoInput)) { _newRowIdx = Math.Clamp(_newRowIdx, 0, ColorTable.NumRows - 1); _newKey = _newKey with { RowIndex = (byte)_newRowIdx }; } + + ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8); } private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled) From 71e15474b2822a9403701458889a93a8f60397c9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 18:32:52 +0100 Subject: [PATCH 068/212] Add deletion of advanced dyes to multi design selection. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index a7afa21..1e52fb5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Designs; +using Glamourer.Interop.Material; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -37,6 +38,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e DrawMultiResetSettings(offset); DrawMultiResetDyes(offset); DrawMultiForceRedraw(offset); + DrawAdvancedButtons(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -57,11 +59,13 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private void ResetCounts() { - _numQuickDesignEnabled = 0; - _numDesignsLocked = 0; - _numDesignsForcedRedraw = 0; - _numDesignsResetSettings = 0; - _numDesignsResetDyes = 0; + _numQuickDesignEnabled = 0; + _numDesignsLocked = 0; + _numDesignsForcedRedraw = 0; + _numDesignsResetSettings = 0; + _numDesignsResetDyes = 0; + _numDesignsWithAdvancedDyes = 0; + _numAdvancedDyes = 0; } private bool CountLeaves(DesignFileSystem.IPath path) @@ -79,6 +83,12 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ++_numDesignsForcedRedraw; if (l.Value.ResetAdvancedDyes) ++_numDesignsResetDyes; + if (l.Value.Materials.Count > 0) + { + ++_numDesignsWithAdvancedDyes; + _numAdvancedDyes += l.Value.Materials.Count; + } + return true; } @@ -135,6 +145,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private int _numDesignsForcedRedraw; private int _numDesignsResetSettings; private int _numDesignsResetDyes; + private int _numAdvancedDyes; + private int _numDesignsWithAdvancedDyes; private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; @@ -294,7 +306,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private void DrawMultiColor(Vector2 width, float offset) { - ImUtf8.TextFrameAligned("Multi Colors:"); + ImUtf8.TextFrameAligned("Multi Colors:"u8); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); @@ -330,6 +342,31 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } + private void DrawAdvancedButtons(float offset) + { + ImUtf8.TextFrameAligned("Delete Adv."u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var enabled = config.DeleteDesignModifier.IsActive(); + var tt = _numDesignsWithAdvancedDyes is 0 + ? "No selected designs contain any advanced dyes." + : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs."; + if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, tt, new Vector2(ImGui.GetContentRegionAvail().X, 0), + !enabled || _numDesignsWithAdvancedDyes is 0)) + + foreach (var design in selector.SelectedPaths.OfType()) + { + while (design.Value.Materials.Count > 0) + editor.ChangeMaterialValue(design.Value, MaterialValueIndex.FromKey(design.Value.Materials[0].Item1), null); + } + + if (!enabled && _numDesignsWithAdvancedDyes is not 0) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); + ImGui.Separator(); + } + + private void DrawApplicationButtons(Vector2 width, float offset) + { } + private void UpdateTagCache() { _addDesigns.Clear(); From 87f1b613f969f5c558acc51220f6d878cd638127 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 00:18:41 +0100 Subject: [PATCH 069/212] Add application shortcuts to multi design panel. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 + .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 104 +++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index caea8fe..9fed566 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -360,6 +360,8 @@ public class DesignPanel equip = false; customize = true; } + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); if (ImUtf8.ButtonEx("Default Application"u8, "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, @@ -369,6 +371,8 @@ public class DesignPanel _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); } + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); ImGui.SameLine(); if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1e52fb5..e0523f7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -39,6 +39,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e DrawMultiResetDyes(offset); DrawMultiForceRedraw(offset); DrawAdvancedButtons(offset); + DrawApplicationButtons(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -364,8 +365,107 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } - private void DrawApplicationButtons(Vector2 width, float offset) - { } + private void DrawApplicationButtons(float offset) + { + ImUtf8.TextFrameAligned("Application"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var width = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var enabled = config.DeleteDesignModifier.IsActive(); + bool? equip = null; + bool? customize = null; + var group = ImUtf8.Group(); + if (ImUtf8.ButtonEx("Disable Everything"u8, + _numDesigns > 0 + ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = false; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Enable Everything"u8, + _numDesigns > 0 + ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = true; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Equipment Only"u8, + _numDesigns > 0 + ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = true; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Customization Only"u8, + _numDesigns > 0 + ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = false; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Default Application"u8, + _numDesigns > 0 + ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." + : "No designs selected.", width, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + { + editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); + editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable Advanced"u8, _numDesigns > 0 + ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + group.Dispose(); + ImGui.Separator(); + if (equip is null && customize is null) + return; + + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + { + editor.ChangeApplyMulti(design, equip, customize, equip, customize, null, equip, equip, equip); + if (equip.HasValue) + { + editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); + editor.ChangeApplyMeta(design, MetaIndex.VisorState, equip.Value); + editor.ChangeApplyMeta(design, MetaIndex.WeaponState, equip.Value); + } + + if (customize.HasValue) + editor.ChangeApplyMeta(design, MetaIndex.Wetness, customize.Value); + } + } private void UpdateTagCache() { From ae093506c19b4a0ad3bc994db86e3fb03b651888 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 3 Mar 2025 23:21:09 +0000 Subject: [PATCH 070/212] [CI] Updating repo.json for testing_1.3.6.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index fe2a7c2..18c30c3 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.5", + "TestingAssemblyVersion": "1.3.6.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2026069ed35f38dd38b12ca703c9d1784219abce Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 15:34:39 +0100 Subject: [PATCH 071/212] Make Glamourer able to export and import color sets to and from Penumbra, maybe. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 72 +++++++++++++++++-- .../Interop/Material/MaterialValueManager.cs | 2 - 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 21e5ef9..e15872b 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,4 +1,5 @@ using Dalamud.Interface; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; @@ -16,6 +17,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Penumbra.String; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Gui.Materials; @@ -262,6 +264,61 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } + private static void CopyToClipboard(in ColorTable.Table table) + { + try + { + fixed (ColorTable.Table* ptr = &table) + { + var data = new ReadOnlySpan(ptr, sizeof(ColorTable.Table)); + var base64 = Convert.ToBase64String(data); + ImGui.SetClipboardText(base64); + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not copy color table to clipboard:\n{ex}"); + } + } + + private static bool ImportFromClipboard(out ColorTable.Table table) + { + try + { + var base64 = ImGui.GetClipboardText(); + if (base64.Length > 0) + { + var data = Convert.FromBase64String(base64); + if (sizeof(ColorTable.Table) <= data.Length) + { + table = new ColorTable.Table(); + fixed (ColorTable.Table* tPtr = &table) + { + fixed (byte* ptr = data) + { + new ReadOnlySpan(ptr, sizeof(ColorTable.Table)).CopyTo(new Span(tPtr, sizeof(ColorTable.Table))); + return true; + } + } + } + } + + if (ColorRowClipboard.IsTableSet) + { + table = ColorRowClipboard.Table; + return true; + } + } + catch (Exception ex) + { + Glamourer.Messager.AddMessage(new Notification(ex, "Could not paste color table from clipboard.", + "Could not paste color table from clipboard.", NotificationType.Error)); + } + + table = default; + return false; + } + private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable.Table table) { using var id = ImRaii.PushId(100); @@ -279,13 +336,17 @@ public sealed unsafe class AdvancedDyePopup( var spacing = ImGui.GetStyle().ItemInnerSpacing.X; ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X); if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this table to your clipboard."u8, buttonSize)) + { ColorRowClipboard.Table = table; + CopyToClipboard(table); + } + ImGui.SameLine(0, spacing); - if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize, - !ColorRowClipboard.IsTableSet)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize) + && ImportFromClipboard(out var newTable)) for (var idx = 0; idx < ColorTable.NumRows; ++idx) { - var row = ColorRowClipboard.Table[idx]; + var row = newTable[idx]; var internalRow = new ColorRow(row); var slot = materialIndex.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand @@ -336,7 +397,7 @@ public sealed unsafe class AdvancedDyePopup( { var rowIndex = index.RowIndex / 2 + 1; var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B'; - ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); + ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); @@ -380,7 +441,8 @@ public sealed unsafe class AdvancedDyePopup( if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8, buttonSize)) ColorRowClipboard.Row = value.Model; ImGui.SameLine(0, spacing.X); - if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, !ColorRowClipboard.IsSet)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, + !ColorRowClipboard.IsSet)) { value.Model = ColorRowClipboard.Row; applied = true; diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 520dacf..01cb479 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -6,8 +6,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Interop.Material; From c9f00c636996c18c384ae47520682fcf1f5a4b61 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 16:28:48 +0100 Subject: [PATCH 072/212] Mark designs containing advanced dyes, mod associations or links in automation. --- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 51 +++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 680e0e9..94329e5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -30,10 +30,10 @@ public class SetPanel( Configuration _config, RandomRestrictionDrawer _randomDrawer) { - private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); + private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)]; - private string? _tempName; - private int _dragIndex = -1; + private string? _tempName; + private int _dragIndex = -1; private Action? _endAction; @@ -178,7 +178,8 @@ public class SetPanel( } else { - ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, + 250 * ImGuiHelpers.GlobalScale - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0)); if (_config.ShowAllAutomatedApplicationRules) ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); @@ -205,8 +206,8 @@ public class SetPanel( if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true)) _endAction = () => _manager.DeleteDesign(Selection, idx); ImGui.TableNextColumn(); - ImUtf8.Selectable($"#{idx + 1:D2}"); - DrawDragDrop(Selection, idx); + DrawSelectable(idx, design.Design); + ImGui.TableNextColumn(); DrawRandomEditing(Selection, design, idx); _designCombo.Draw(Selection, design, idx); @@ -243,6 +244,44 @@ public class SetPanel( _endAction = null; } + private void DrawSelectable(int idx, IDesignStandIn design) + { + var highlight = 0u; + var sb = new StringBuilder(); + if (design is Design d) + { + var count = design.AllLinks(true).Count(); + if (count > 1) + { + sb.AppendLine($"This design contains {count - 1} links to other designs."); + highlight = ColorId.HeaderButtons.Value(); + } + + count = d.AssociatedMods.Count; + if (count > 0) + { + sb.AppendLine($"This design contains {count} mod associations."); + highlight = ColorId.ModdedItemMarker.Value(); + } + + count = design.GetMaterialData().Count(p => p.Item2.Enabled); + if (count > 0) + { + sb.AppendLine($"This design contains {count} enabled advanced dyes."); + highlight = ColorId.AdvancedDyeActive.Value(); + } + } + + using (ImRaii.PushColor(ImGuiCol.Text, highlight, highlight != 0)) + { + ImUtf8.Selectable($"#{idx + 1:D2}"); + } + + ImUtf8.HoverTooltip($"{sb}"); + + DrawDragDrop(Selection, idx); + } + private int _tmpGearset = int.MaxValue; private int _whichIndex = -1; From 773682838e553e966d0dd75e0294113c8e54f305 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 13:37:27 +0100 Subject: [PATCH 073/212] Make customize buttons not change advanced customization to On by default. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 9fed566..04e51ce 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -386,7 +386,7 @@ public class DesignPanel if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize, null, equip, equip, equip); + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index e0523f7..2235160 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -454,7 +454,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) { - editor.ChangeApplyMulti(design, equip, customize, equip, customize, null, equip, equip, equip); + editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); From e93c3b7bb8872d73a68362190112789dde0825c1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:12:21 +0100 Subject: [PATCH 074/212] Update submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index c347d29..13f1a90 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit c347d29d980b0191d1d071170cf2ec229e3efdcf +Subproject commit 13f1a90b88d2b8572480748a209f957b70d6a46f diff --git a/Penumbra.GameData b/Penumbra.GameData index a21c146..96163f7 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a21c146790b370bd58b0f752385ae153f7e769c0 +Subproject commit 96163f79e13c7d52cc36cdd82ab4e823763f4f31 From 3dee511b9adb9a1da98442619d9e8093dab0caa5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:13:33 +0100 Subject: [PATCH 075/212] Update some obsoletes. --- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 2 +- Glamourer/Interop/InventoryService.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index b021656..f57a57e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -23,7 +23,7 @@ public unsafe class InventoryPanel : IGameDataDrawer ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}"); var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems); - if (equip == null || equip->Loaded == 0) + if (equip == null || equip->IsLoaded) return; ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}"); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 4b98d46..c30ae06 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -182,7 +182,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService // Invoked after calling Original, so the item is already moved. var inventory = manager->GetInventoryContainer(targetContainer); - if (inventory == null || inventory->Loaded == 0 || inventory->Size <= targetSlot) + if (inventory == null || inventory->IsLoaded || inventory->Size <= targetSlot) return false; var item = inventory->GetInventorySlot((int)targetSlot); diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 0fc1675..6708267 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -168,7 +168,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGetInventoryContainer(type); - if (container != null && container->Loaded != 0 && _currentInventoryIndex < container->Size) + if (container != null && container->IsLoaded && _currentInventoryIndex < container->Size) { Glamourer.Log.Excessive($"[UnlockScanner] Scanning {_currentInventory} {type} {_currentInventoryIndex}/{container->Size}."); var item = container->GetInventorySlot(_currentInventoryIndex++); From 381b2a1b8b198f1569b23139e225f738842f2731 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:14:19 +0100 Subject: [PATCH 076/212] 1.3.7.0 --- Glamourer/Gui/GlamourerChangelog.cs | 63 +++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 4c8b365..7033fdd 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -40,6 +40,7 @@ public class GlamourerChangelog Add1_3_4_0(Changelog); Add1_3_5_0(Changelog); Add1_3_6_0(Changelog); + Add1_3_7_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -60,32 +61,67 @@ public class GlamourerChangelog } } + private static void Add1_3_7_0(Changelog log) + => log.NextVersion("Version 1.3.7.0") + .RegisterImportant( + "The option to disable advanced customizations or advanced dyes has been removed. The functionality can no longer be disabled entirely, you can just decide not to use it, and to hide it.") + .RegisterHighlight( + "You can now configure which panels (like Customization, Equipment, Advanced Customization etc.) are displayed at all, and which are expanded by default. This does not disable any functionality.") + .RegisterHighlight( + "The Unlocks tab now shows whether items are modded in the currently selected collection in Penumbra in Overview mode and shows and can filter and sort for it in Detailed mode.") + .RegisterEntry("Added an optional button to the Quick Design Bar to reset all temporary settings applied by Glamourer.") + .RegisterHighlight( + "Any existing advanced dyes will now be highlighted on the corresponding Advanced Dye buttons in the actors panel and on the corresponding equip slot name in the design panel.") + .RegisterEntry("This also affects currently inactive advanced dyes, which can now be manually removed on the inactive materials.", + 1) + .RegisterHighlight( + "In the design list of an automation set, the design indices are now highlighted if a design contains advanced dyes, mod associations, or links to other designs.") + .RegisterHighlight("Some quality of life improvements:") + .RegisterEntry("Added some buttons for some application rule presets to the Application Rules panel.", 1) + .RegisterEntry("Added some buttons to enable, disable or delete all advanced dyes in a design.", 1) + .RegisterEntry("Some of those buttons are also available in multi-design selection to apply to all selected designs at once.", 1) + .RegisterEntry( + "A copied material color set from Penumbra should now be able to be imported into a advanced dye color set, as well as the other way around.") + .RegisterEntry( + "Automatically applied character updates when applying a design with mod associations and temporary settings are now skipped to prevent some issues with GPose. This should not affect anything else.") + .RegisterEntry("Glamourer now differentiates between temporary settings applied through manual or automatic application."); + + private static void Add1_3_6_0(Changelog log) => log.NextVersion("Version 1.3.6.0") .RegisterHighlight("Added some new multi design selection functionality to change design settings of many designs at once.") .RegisterEntry("Also added the number of selected designs and folders to the multi design selection display.", 1) .RegisterEntry("Glamourer will now use temporary settings when saving mod associations, if they exist in Penumbra.") - .RegisterEntry("Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") - .RegisterEntry("Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") - .RegisterEntry("Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") + .RegisterEntry( + "Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") + .RegisterEntry( + "Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") + .RegisterEntry( + "Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") .RegisterEntry("All existing facepaints should now be accepted in designs, including NPC facepaints.") - .RegisterEntry("Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") + .RegisterEntry( + "Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") .RegisterEntry("Fixed an issue with racial mount and accessory scaling when changing zones on a changed race.") .RegisterEntry("Fixed issues with the detection of gear set changes in certain circumstances (Thanks Cordelia).") .RegisterEntry("Fixed an issue with the Force to Inherit checkbox in mod associations.") - .RegisterEntry("Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") + .RegisterEntry( + "Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") .RegisterEntry("Added new IPC to set a meta flag on actors. (for/from Cordelia)."); private static void Add1_3_5_0(Changelog log) => log.NextVersion("Version 1.3.5.0") - .RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") + .RegisterHighlight( + "Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") .RegisterEntry("Designs now have a setting to always reset all prior temporary settings made by Glamourer on application.", 1) - .RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1) + .RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1) .RegisterHighlight("More NPC customization options should now be accepted as valid for designs, regardless of clan/gender.") .RegisterHighlight("The 'Apply' chat command had the currently selected design and the current quick bar design added as choices.") - .RegisterEntry("The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.") + .RegisterEntry( + "The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.") .RegisterHighlight("Randomly chosen designs should now stay across loading screens or redrawing. (1.3.4.3)") - .RegisterEntry("In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", 1) + .RegisterEntry( + "In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", + 1) .RegisterEntry("Fixed an issue where disabling auto designs did not work as expected.") .RegisterEntry("Fixed the inversion of application flags in IPC calls.") .RegisterEntry("Fixed an issue with the scaling of the Advanced Dye popup with increased font sizes.") @@ -110,15 +146,18 @@ public class GlamourerChangelog => log.NextVersion("Version 1.3.2.0") .RegisterEntry("Fixed an issue with weapon hiding when leaving GPose or changing zones.") .RegisterEntry("Added support for unnamed items to be previewed from Penumbra.") - .RegisterEntry("Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.") + .RegisterEntry( + "Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.") .RegisterEntry("Improved the handling of bonus items (glasses) in designs.") .RegisterEntry("Imported .chara files now import bonus items.") - .RegisterEntry("Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.") + .RegisterEntry( + "Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.") .RegisterEntry("Fixed bonus items not reverting correctly in some cases.") .RegisterEntry("Fixed an issue with the RNG in cheat codes and events skipping some possible entries.") .RegisterEntry("Fixed the chat log context menu for glamourer Try-On.") .RegisterEntry("Fixed some issues with cheat code sets.") - .RegisterEntry("Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.") + .RegisterEntry( + "Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.") .RegisterEntry("Refreshed NPC name associations.") .RegisterEntry("Removed a now useless cheat code.") .RegisterEntry("Added API for Bonus Items. (1.3.1.1)"); From 99a8e1e8c5613bafb1ce9cf6f04d7dfe575b6d7b Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 9 Mar 2025 22:17:20 +0000 Subject: [PATCH 077/212] [CI] Updating repo.json for 1.3.7.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 18c30c3..cc9fb7a 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.6", + "AssemblyVersion": "1.3.7.0", + "TestingAssemblyVersion": "1.3.7.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 25517525c90a8a16992577f6a150231357506db6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 10 Mar 2025 23:55:24 +0100 Subject: [PATCH 078/212] Keep temporary mod settings manually applied by Glamourer when redrawing with automation changes. --- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 4 +- .../Interop/Penumbra/ModSettingApplier.cs | 4 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 41 +++++++++++++++---- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index f172735..80000d9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -87,7 +87,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void ApplyAll() { foreach (var (mod, settings) in selector.Selected!.AssociatedMods) - penumbra.SetMod(mod, settings, StateSource.Manual); + penumbra.SetMod(mod, settings, StateSource.Manual, false); } private void DrawTable() @@ -222,7 +222,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) { - var text = penumbra.SetMod(mod, settings, StateSource.Manual); + var text = penumbra.SetMod(mod, settings, StateSource.Manual, false); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 82a840c..1d39f79 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -39,7 +39,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var index = ResetOldSettings(collection, actor, source, design.ResetTemporarySettings, respectManual); foreach (var (mod, setting) in design.AssociatedMods) { - var message = penumbra.SetMod(mod, setting, source, collection, index); + var message = penumbra.SetMod(mod, setting, source, respectManual, collection, index); if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else @@ -62,7 +62,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var index = ResetOldSettings(collection, actor, source, resetOther, true); foreach (var (mod, setting) in settings) { - var message = penumbra.SetMod(mod, setting, source, collection, index); + var message = penumbra.SetMod(mod, setting, source, false, collection, index); if (message.Length > 0) messages.Add($"Error applying mod settings: {message}"); else diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index d9b4d27..e04af7c 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -41,9 +41,9 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp3 = 6; public const int RequiredPenumbraFeatureVersionTemp4 = 7; - private const int KeyFixed = -1610; - private const string NameFixed = "Glamourer (Automation)"; - private const int KeyManual = -6160; + private const int KeyFixed = -1610; + private const string NameFixed = "Glamourer (Automation)"; + private const int KeyManual = -6160; private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; @@ -77,6 +77,7 @@ public class PenumbraService : IDisposable 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.QueryTemporaryModSettingsPlayer? _queryTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; @@ -280,7 +281,8 @@ public class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, StateSource source, Guid? collectionInput = null, ObjectIndex? index = null) + public string SetMod(Mod mod, ModSettings settings, StateSource source, bool respectManual, Guid? collectionInput = null, + ObjectIndex? index = null) { if (!Available) return "Penumbra is not available."; @@ -290,7 +292,7 @@ public class PenumbraService : IDisposable { var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; if (_config.UseTemporarySettings && _setTemporaryModSettings != null) - SetModTemporary(sb, mod, settings, collection, index, source); + SetModTemporary(sb, mod, settings, collection, respectManual, index, source); else SetModPermanent(sb, mod, settings, collection); @@ -326,9 +328,29 @@ public class PenumbraService : IDisposable public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; - private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index, StateSource source) + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, bool respectManual, ObjectIndex? index, + StateSource source) { var (key, name) = source.IsFixed() ? (KeyFixed, NameFixed) : (KeyManual, NameManual); + // Check for existing manual settings and do not apply fixed on top of them if respecting manual changes. + if (key is KeyFixed && respectManual) + { + var existingSource = string.Empty; + var ec = index.HasValue + ? _queryTemporaryModSettingsPlayer?.Invoke(index.Value.Index, mod.DirectoryName, out _, + out existingSource, key, mod.Name) + ?? PenumbraApiEc.InvalidArgument + : _queryTemporaryModSettings?.Invoke(collection, mod.DirectoryName, out _, + out existingSource, key, mod.Name) + ?? PenumbraApiEc.InvalidArgument; + if (ec is PenumbraApiEc.Success && existingSource is NameManual) + { + Glamourer.Log.Debug( + $"Skipped applying mod settings for [{mod.Name}] through automation because manual settings from Glamourer existed."); + return; + } + } + var ex = settings.Remove ? index.HasValue ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) @@ -391,9 +413,7 @@ public class PenumbraService : IDisposable : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); switch (ec) { - case PenumbraApiEc.OptionGroupMissing: - sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); - break; + case PenumbraApiEc.OptionGroupMissing: sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); break; case PenumbraApiEc.OptionMissing: sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}."); break; @@ -527,6 +547,8 @@ public class PenumbraService : IDisposable if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + _queryTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp3) { _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); @@ -586,6 +608,7 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; _queryTemporaryModSettings = null; + _queryTemporaryModSettingsPlayer = null; _getChangedItems = null; _changedItems = null; _checkCurrentChangedItems = null; From 750d4f9eca78f1dae713065d099780a1bade9789 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 10 Mar 2025 22:59:41 +0000 Subject: [PATCH 079/212] [CI] Updating repo.json for 1.3.7.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index cc9fb7a..f2b213e 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.7.0", - "TestingAssemblyVersion": "1.3.7.0", + "AssemblyVersion": "1.3.7.1", + "TestingAssemblyVersion": "1.3.7.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From fd0d761b9270698557e13cda343015fa8e30016f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Mar 2025 00:58:00 +0100 Subject: [PATCH 080/212] Fix small issue with invisible customizations applying. --- Glamourer/Automation/ApplicationType.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 8871a0e..58f296e 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -47,7 +47,13 @@ public static class ApplicationTypeExtensions } public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn) - => designStandIn is not DesignBase design ? type.Collection() : type.Collection().Restrict(design.Application); + { + if(designStandIn is not DesignBase design) + return type.Collection(); + var ret = type.Collection().Restrict(design.Application); + ret.CustomizeRaw = ret.CustomizeRaw.FixApplication(design.CustomizeSet); + return ret; + } public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; From 18ff905746225c81f42ae4d021a7db45d884f1de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Mar 2025 18:06:22 +0100 Subject: [PATCH 081/212] Add a chat command to clear temporary settings made by Glamourer. --- Glamourer/Services/CommandService.cs | 90 ++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 4b8ea3d..fa6e63f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -41,12 +41,13 @@ public class CommandService : IDisposable, IApiService private readonly DesignManager _designManager; private readonly DesignConverter _converter; private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, - QuickDesignCombo quickDesignCombo, DesignResolver resolver) + QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) { _commands = commands; _mainWindow = mainWindow; @@ -63,6 +64,7 @@ public class CommandService : IDisposable, IApiService _items = items; _customizeService = customizeService; _resolver = resolver; + _penumbra = penumbra; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -122,8 +124,9 @@ public class CommandService : IDisposable, IApiService "reapply" => ReapplyState(argument), "revert" => Revert(argument), "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false, false), - "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), - "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), + "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), + "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), + "clearsettings" => ClearSettings(argument), "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), @@ -154,6 +157,8 @@ public class CommandService : IDisposable, IApiService "Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("resetdesign", "Reapplies the current automation and resets the random design. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddCommand("clearsettings", "Clears all temporary settings applied by Glamourer. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() @@ -168,6 +173,62 @@ public class CommandService : IDisposable, IApiService return true; } + private bool ClearSettings(string argument) + { + var argumentList = argument.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (argumentList.Length < 1) + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour clearsettings ").AddGreen("[Character Identifier]").AddText(" | ") + .AddPurple("").AddText(" | ").AddBlue("").BuiltString); + PlayerIdentifierHelp(false, true); + _chat.Print(new SeStringBuilder().AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all").AddText("' to clear all collections.").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》 The booleans are optional and default to 'true', the ").AddPurple("first") + .AddText(" determines whether ").AddPurple("manually").AddText(" applied settings are cleared, the ").AddBlue("second") + .AddText(" determines whether ").AddBlue("automatically").AddText(" applied settings are cleared.").BuiltString); + return false; + } + + var clearManual = true; + var clearAutomatic = true; + if (argumentList.Length > 1 && bool.TryParse(argumentList[1], out var m)) + clearManual = m; + if (argumentList.Length > 2 && bool.TryParse(argumentList[2], out var a)) + clearAutomatic = a; + + if (!clearManual && !clearAutomatic) + return true; + + if (argumentList[0].ToLowerInvariant() is "all") + { + _penumbra.ClearAllTemporarySettings(clearAutomatic, clearManual); + return true; + } + + if (!IdentifierHandling(argumentList[0], out var identifiers, false, true)) + return false; + + var set = new HashSet(); + foreach (var id in identifiers) + { + if (!_objects.TryGetValue(id, out var data) || !data.Valid) + continue; + + foreach (var obj in data.Objects) + { + var guid = _penumbra.GetActorCollection(obj, out _); + if (!set.Add(guid)) + continue; + + if (clearManual) + _penumbra.RemoveAllTemporarySettings(guid, StateSource.Manual); + if (clearAutomatic) + _penumbra.RemoveAllTemporarySettings(guid, StateSource.Fixed); + } + } + + return true; + } + private bool SetAutomation(string arguments) { var argumentList = arguments.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); @@ -283,21 +344,11 @@ public class CommandService : IDisposable, IApiService { switch (char.ToLowerInvariant(character)) { - case 'c': - applicationFlags |= ApplicationType.Customizations; - break; - case 'e': - applicationFlags |= ApplicationType.Armor; - break; - case 'a': - applicationFlags |= ApplicationType.Accessories; - break; - case 'd': - applicationFlags |= ApplicationType.GearCustomization; - break; - case 'w': - applicationFlags |= ApplicationType.Weapons; - break; + case 'c': applicationFlags |= ApplicationType.Customizations; break; + case 'e': applicationFlags |= ApplicationType.Armor; break; + case 'a': applicationFlags |= ApplicationType.Accessories; break; + case 'd': applicationFlags |= ApplicationType.GearCustomization; break; + case 'w': applicationFlags |= ApplicationType.Weapons; break; default: _chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true) .AddText(" is not a valid set of application flags.").BuiltString); @@ -694,7 +745,8 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); + var (messages, appliedMods, _, name, overridden) = + _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); From 22babad7896ac3ae227087e8dbafce45a0420a28 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Mar 2025 18:11:08 +0100 Subject: [PATCH 082/212] Update. --- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- Glamourer.Api | 2 +- Glamourer.sln | 48 ++++---- Glamourer/Glamourer.csproj | 64 +---------- Glamourer/Glamourer.json | 2 +- Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Interop/Material/DirectXService.cs | 11 +- Glamourer/Interop/Material/MaterialService.cs | 12 +- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 4 +- Glamourer/packages.lock.json | 108 ++++++++++++++++++ OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 18 files changed, 160 insertions(+), 111 deletions(-) create mode 100644 Glamourer/packages.lock.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bac600a..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.x.x' + dotnet-version: '9.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index b9d3672..6316776 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.x.x' + dotnet-version: '9.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/Glamourer.Api b/Glamourer.Api index 9f9bdf0..5e5c867 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 9f9bdf0873899d2e45fabaca446bb1624303b418 +Subproject commit 5e5c867a095eecac0dd494b30a33298a65e46426 diff --git a/Glamourer.sln b/Glamourer.sln index 4ac3356..78c32ec 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -29,30 +29,30 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|Any CPU + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9a1b95b..5fa2c5d 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,38 +1,13 @@ - + - net8.0-windows - preview - x64 Glamourer Glamourer 9.0.0.1 9.0.0.1 - SoftOtter Glamourer - Copyright © 2023 - true - Library + Copyright © 2025 4 - true - enable bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - true - false - false - - - - true - full - false - DEBUG;TRACE - - - - pdbonly - true - TRACE @@ -47,41 +22,6 @@ - - $(AppData)\XIVLauncher\addon\Hooks\dev\ - - - - - $(DalamudLibPath)Dalamud.dll - False - - - $(DalamudLibPath)FFXIVClientStructs.dll - False - - - $(DalamudLibPath)ImGui.NET.dll - False - - - $(DalamudLibPath)ImGuiScene.dll - False - - - $(DalamudLibPath)Lumina.dll - False - - - $(DalamudLibPath)Lumina.Excel.dll - False - - - $(DalamudLibPath)Newtonsoft.Json.dll - False - - - diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 1e9edf7..3127d7d 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 11, + "DalamudApiLevel": 12, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d2dba01..c9ace0c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -173,7 +173,7 @@ public sealed class DesignQuickBar : Window, IDisposable available |= 2; _tooltipBuilder.Append("Right-Click: Apply ") .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) - .Append(" to {_targetIdentifier}."); + .Append(" to ").Append(_config.Ephemeral.IncognitoMode ? _targetIdentifier.Incognito(null) : _targetIdentifier.ToName()); } if (available == 0) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index e15872b..ec25378 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -94,7 +94,7 @@ public sealed unsafe class AdvancedDyePopup( : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); var gamePath = modelHandle == null ? string.Empty - : modelHandle->GetMaterialFileNameBySlotAsString(index.MaterialIndex); + : modelHandle->GetMaterialFileNameBySlot(index.MaterialIndex).ToString(); return (path, gamePath); } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 68ba18e..cf7e1f3 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -29,7 +29,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable .Select(p => new KeyValuePair(p.First, p.Second)); public ChangedItemType LastType { get; private set; } = ChangedItemType.None; - public uint LastId { get; private set; } = 0; + public uint LastId { get; private set; } public DateTime LastTooltip { get; private set; } = DateTime.MinValue; public DateTime LastClick { get; private set; } = DateTime.MinValue; diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index a809a34..8006a2f 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -1,12 +1,9 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Lumina.Data.Files; using OtterGui.Services; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.String.Functions; using SharpGen.Runtime; using Vortice.Direct3D11; -using Vortice.DXGI; using MapFlags = Vortice.Direct3D11.MapFlags; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; @@ -14,7 +11,7 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { - private readonly object _lock = new(); + private readonly object _lock = new(); private readonly ConcurrentDictionary _textures = []; /// Generate a color table the way the game does inside the original texture, and release the original. @@ -32,9 +29,7 @@ public unsafe class DirectXService(IFramework framework) : IService lock (_lock) { - using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, - (uint)TexFile.TextureFormat.R16G16B16A16F, - (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false); + using var texture = new SafeTextureHandle(MaterialService.CreateColorTableTexture(), false); if (texture.IsInvalid) return false; @@ -119,7 +114,7 @@ public unsafe class DirectXService(IFramework framework) : IService { var desc = resource.Description1; - if (desc.Format is not Format.R16G16B16A16_Float + if (desc.Format is not Vortice.DXGI.Format.R16G16B16A16_Float || desc.Width != MaterialService.TextureWidth || desc.Height != MaterialService.TextureHeight || map.DepthPitch != map.RowPitch * desc.Height) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index f7ffe0f..a5f2b36 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -9,18 +9,24 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { + private const TextureFormat Format = TextureFormat.R16G16B16A16_FLOAT; + private const TextureFlags Flags = TextureFlags.TextureType2D | TextureFlags.Managed | TextureFlags.Immutable; + public const int TextureWidth = 8; public const int TextureHeight = ColorTable.NumRows; public const int MaterialsPerModel = 10; - public static bool GenerateNewColorTable(in ColorTable.Table colorTable, out Texture* texture) + public static Texture* CreateColorTableTexture() { var textureSize = stackalloc int[2]; textureSize[0] = TextureWidth; textureSize[1] = TextureHeight; + return Device.Instance()->CreateTexture2D(textureSize, 1, Format, Flags, 7); + } - texture = Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F, - (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7); + public static bool GenerateNewColorTable(in ColorTable.Table colorTable, out Texture* texture) + { + texture = CreateColorTableTexture(); if (texture == null) return false; diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index b44246b..4d1feef 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -133,7 +133,7 @@ public sealed unsafe class PrepareColorSet public static ColorRow.Mode GetMode(MaterialResourceHandle* handle) => handle == null ? ColorRow.Mode.Dawntrail - : handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8) + : handle->ShpkName.AsSpan().SequenceEqual("characterlegacy.shpk"u8) ? ColorRow.Mode.Legacy : ColorRow.Mode.Dawntrail; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 18f3cac..b58385e 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -190,7 +190,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable ? "Eternal Bond" : x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Aesthetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(hair, (x.Value.Data, name)); + ret.TryAdd(hair, (x.Value.UnlockLink, name)); } } @@ -201,7 +201,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable { var name = x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Cosmetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(paint, (x.Value.Data, name)); + ret.TryAdd(paint, (x.Value.UnlockLink, name)); } } } diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json new file mode 100644 index 0000000..e6f2fe5 --- /dev/null +++ b/Glamourer/packages.lock.json @@ -0,0 +1,108 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows7.0": { + "DalamudPackager": { + "type": "Direct", + "requested": "[12.0.0, )", + "resolved": "12.0.0", + "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" + }, + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.2.25, )", + "resolved": "1.2.25", + "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + }, + "Vortice.Direct3D11": { + "type": "Direct", + "requested": "[3.4.2-beta, )", + "resolved": "3.4.2-beta", + "contentHash": "CWVMTF7ebylzzXbQXVp5C9UpBB/L+EpX2OxSdb2wlzcsdEmrev/Ith8wVs0WjZ6DbA0WiiybnYAWqB5v0nOO/A==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "Vortice.DXGI": "3.4.2-beta" + } + }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "9.0.2", + "contentHash": "ZffbJrskOZ40JTzcTyKwFHS5eACSWp2bUQBBApIgGV+es8RaTD4OxUG7XxFr3RIPLXtYQ1jQzF2DjKB5fZn7Qg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "9.0.2", + "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg==" + }, + "SharpGen.Runtime": { + "type": "Transitive", + "resolved": "2.1.2-beta", + "contentHash": "nqZAjfEG1jX1ivvdZLsi6Pkt0DiOJyuOgRgldNFsmjXFPhxUbXQibofLSwuDZidL2kkmtTF8qLoRIeqeVdXgYw==" + }, + "SharpGen.Runtime.COM": { + "type": "Transitive", + "resolved": "2.1.2-beta", + "contentHash": "HBCrb6HfnUWx9v5/GjJeBr5DuodZLnHlFQQYXPrQs1Hbe1c6Wd0uCXf+SJp4hW8fQNxjXEu0FgiyHGlA/SRzRw==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta" + } + }, + "Vortice.DirectX": { + "type": "Transitive", + "resolved": "3.4.2-beta", + "contentHash": "EwDbemXkmEiDGZVDem25uiEcZBYOMb+wzePuta+M/k2LXrQVGPknZhZUK56+QlHhI+Ducf/d+J75wgBzEjKi2g==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "SharpGen.Runtime.COM": "2.1.2-beta", + "Vortice.Mathematics": "1.7.6" + } + }, + "Vortice.DXGI": { + "type": "Transitive", + "resolved": "3.4.2-beta", + "contentHash": "T4S3pp6l/SGJ6SH3ebCbodN/bimGOkIBiIYKeBpVEis7+/ac1XIjyzgSTJ5XsH3o3hSH7DqSbP6Yo6mL9nyFQA==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "Vortice.DirectX": "3.4.2-beta" + } + }, + "Vortice.Mathematics": { + "type": "Transitive", + "resolved": "1.7.6", + "contentHash": "W8FNv850lPGxmHphwLyi1qnUlQHZBxh/62EenFJTaY6acPP29Fk0xMQJI60G+YNlsVJb3fSoriuW+ong5sM5UQ==" + }, + "glamourer.api": { + "type": "Project" + }, + "ottergui": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2024.3.0, )", + "Microsoft.Extensions.DependencyInjection": "[9.0.2, )" + } + }, + "penumbra.api": { + "type": "Project" + }, + "penumbra.gamedata": { + "type": "Project", + "dependencies": { + "OtterGui": "[1.0.0, )", + "Penumbra.Api": "[5.6.1, )", + "Penumbra.String": "[1.0.6, )" + } + }, + "penumbra.string": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/OtterGui b/OtterGui index 13f1a90..3396ee1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 13f1a90b88d2b8572480748a209f957b70d6a46f +Subproject commit 3396ee176fa72ad2dfb2de3294f7125ebce4dae5 diff --git a/Penumbra.Api b/Penumbra.Api index 70f0468..2cbf4ba 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 70f046830cc7cd35b3480b12b7efe94182477fbb +Subproject commit 2cbf4bace53a5749d3eab1ff03025a6e6bd9fc37 diff --git a/Penumbra.GameData b/Penumbra.GameData index 96163f7..9ae4a97 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 96163f79e13c7d52cc36cdd82ab4e823763f4f31 +Subproject commit 9ae4a97110fff005a54213815086ce950d4d8b2d diff --git a/Penumbra.String b/Penumbra.String index 4eb7c11..2896c05 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 4eb7c118cdac5873afb97cb04719602f061f03b7 +Subproject commit 2896c0561f60827f97408650d52a15c38f4d9d10 From 00cb1b6643615fa360a5d4c11a44e0481f0820d9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 13:52:15 +0100 Subject: [PATCH 083/212] Use CS sig. --- Glamourer/Interop/ScalingService.cs | 5 +++-- Penumbra.GameData | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 68dc2e8..2a89a25 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -27,6 +27,9 @@ public unsafe class ScalingService : IDisposable interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _calculateHeightHook = interop.HookFromAddress((nint)ModelContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); + _placeMinionHook = interop.HookFromAddress((nint)Companion.MemberFunctionPointers.PlaceCompanion, PlaceMinionDetour); + //_updateOrnamentHook = + // interop.HookFromAddress((nint)Ornament.MemberFunctionPointers.UpdateOrnament, UpdateOrnamentDetour); _setupMountHook.Enable(); _updateOrnamentHook.Enable(); @@ -55,8 +58,6 @@ public unsafe class ScalingService : IDisposable private readonly Hook _calculateHeightHook; - // TODO: Use client structs sig. - [Signature(Sigs.PlaceMinion, DetourName = nameof(PlaceMinionDetour))] private readonly Hook _placeMinionHook = null!; private void SetupMountDetour(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) diff --git a/Penumbra.GameData b/Penumbra.GameData index 9ae4a97..8ec54ed 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 9ae4a97110fff005a54213815086ce950d4d8b2d +Subproject commit 8ec54ed4b114e2ffb1d46596785fd5ec382d7ebd From d6d592f099a8a51065465f95c1a7dcd216b06026 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 13:58:19 +0100 Subject: [PATCH 084/212] 1.3.8.0 --- Glamourer.sln | 1 + Glamourer/Gui/GlamourerChangelog.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Glamourer.sln b/Glamourer.sln index 78c32ec..e2915d5 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\release.yml = .github\workflows\release.yml + Glamourer\Glamourer.json = Glamourer\Glamourer.json repo.json = repo.json .github\workflows\test_release.yml = .github\workflows\test_release.yml EndProjectSection diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 7033fdd..e12b32e 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -41,6 +41,7 @@ public class GlamourerChangelog Add1_3_5_0(Changelog); Add1_3_6_0(Changelog); Add1_3_7_0(Changelog); + Add1_3_8_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -61,6 +62,18 @@ public class GlamourerChangelog } } + private static void Add1_3_8_0(Changelog log) + => log.NextVersion("Version 1.3.8.0") + .RegisterImportant("Updated Glamourer for update 7.20 and Dalamud API 12.") + .RegisterEntry( + "This is not thoroughly tested, but I decided to push to stable instead of testing because otherwise a lot of people would just go to testing just for early access again despite having no business doing so.", + 1) + .RegisterEntry( + "I also do not use most of the functionality of Glamourer myself, so I am unable to even encounter most issues myself.", 1) + .RegisterEntry("If you encounter any issues, please report them quickly on the discord.", 1) + .RegisterEntry("Added a chat command to clear temporary settings applied by Glamourer to Penumbra.") + .RegisterEntry("Fixed small issues with customizations not applicable to your race still applying."); + private static void Add1_3_7_0(Changelog log) => log.NextVersion("Version 1.3.7.0") .RegisterImportant( From 9d3dfbbece555b3505b86e63a1ac68d5700ad283 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:05:53 +0100 Subject: [PATCH 085/212] use staging build for release for now. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 296f1e90b5c0c3b18b3f79ba68a5bd5fd338a089 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:13:34 +0100 Subject: [PATCH 086/212] :shrug: --- Glamourer/Glamourer.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 5fa2c5d..a4325fb 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -10,10 +10,6 @@ bin\$(Configuration)\ - - OnOutputUpdated - - @@ -62,8 +58,4 @@ PreserveNewest - - - - \ No newline at end of file From d75d70bee577680da38b95dad877b1da92838c1e Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Mar 2025 13:16:18 +0000 Subject: [PATCH 087/212] [CI] Updating repo.json for 1.3.8.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index f2b213e..2eabfd0 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.7.1", - "TestingAssemblyVersion": "1.3.7.1", + "AssemblyVersion": "1.3.8.0", + "TestingAssemblyVersion": "1.3.8.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d398381b52f1d22625adfce15e0ad044c7146e95 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:17:16 +0100 Subject: [PATCH 088/212] Revert Dalamud staging on release, and update api level. --- .github/workflows/release.yml | 2 +- repo.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/repo.json b/repo.json index 2eabfd0..fd45265 100644 --- a/repo.json +++ b/repo.json @@ -21,8 +21,8 @@ "TestingAssemblyVersion": "1.3.8.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 11, - "TestingDalamudApiLevel": 11, + "DalamudApiLevel": 12, + "TestingDalamudApiLevel": 12, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From b0abf865cb2e189f3864295a7d875f6e4b2e4f55 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 15:54:10 +0100 Subject: [PATCH 089/212] Change build step. --- Glamourer/Glamourer.csproj | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index a4325fb..c3e46ab 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -12,6 +12,10 @@ + + + PreserveNewest + @@ -52,10 +56,4 @@ $(GitCommitHash) - - - - PreserveNewest - - \ No newline at end of file From b1d00e981240221fb3a98f7c3f545c7c6f2fb1e1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 15:54:26 +0100 Subject: [PATCH 090/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 8ec54ed..8592159 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 8ec54ed4b114e2ffb1d46596785fd5ec382d7ebd +Subproject commit 859215989da41a4ccb59a5ce390223570a69c94e From 361ed536a3056bbd7705cb6dcd16e5c1589bfa1b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:22:57 +0100 Subject: [PATCH 091/212] Fix issue with NPC automation due to missing job detection. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 8592159..ff9b731 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 859215989da41a4ccb59a5ce390223570a69c94e +Subproject commit ff9b731c39494851cfde3d580cd99364b6c04d7c From 76eaa75d04e7cf4373476e8a2dacaf6266e460ba Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:23:25 +0100 Subject: [PATCH 092/212] Update Penumbra.Api. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 2cbf4ba..bd56d82 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 2cbf4bace53a5749d3eab1ff03025a6e6bd9fc37 +Subproject commit bd56d82816b8366e19dddfb2dc7fd7f167e264ee From 782c4446b2ae78602c90331c6ee1bf8f9af24897 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:25:34 +0100 Subject: [PATCH 093/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index ff9b731..e717a66 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ff9b731c39494851cfde3d580cd99364b6c04d7c +Subproject commit e717a66f33b0656a7c5c971ffa2f63fd96477d94 From 381b23fe0cbb9d8ed21c129c35f84113687ac21f Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Mar 2025 16:29:25 +0000 Subject: [PATCH 094/212] [CI] Updating repo.json for 1.3.8.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index fd45265..d29a69f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.0", - "TestingAssemblyVersion": "1.3.8.0", + "AssemblyVersion": "1.3.8.1", + "TestingAssemblyVersion": "1.3.8.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b1e65e6f9d509c84454c0657da8f3936e9ca9f2d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 29 Mar 2025 18:04:13 +0100 Subject: [PATCH 095/212] Fix some offsets. --- Glamourer/Interop/Material/MaterialManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 81373c5..e065e91 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,12 +197,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { - // TODO: Use ClientStructs - var changedData = *(void**)((byte*)weapon + 0xA40); + var changedData = weapon->ChangedData; if (changedData == null) return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); - return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], - new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); + return new CharacterWeapon(weapon->ModelSetId, changedData->SecondaryId, changedData->Variant, + new StainIds(changedData->Stain0, changedData->Stain1)); } } From 4fca1600cab8c954789171eb81feeecf5968c5dc Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 29 Mar 2025 17:06:25 +0000 Subject: [PATCH 096/212] [CI] Updating repo.json for 1.3.8.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index d29a69f..bba0aa8 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.1", - "TestingAssemblyVersion": "1.3.8.1", + "AssemblyVersion": "1.3.8.2", + "TestingAssemblyVersion": "1.3.8.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6b3a64ce14ad1f033c23afe17b641021eaffdd73 Mon Sep 17 00:00:00 2001 From: keifufu Date: Mon, 31 Mar 2025 13:00:51 +0200 Subject: [PATCH 097/212] fix linux build --- Glamourer/Glamourer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index c3e46ab..90c743e 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -26,7 +26,7 @@ - + @@ -56,4 +56,4 @@ $(GitCommitHash) - \ No newline at end of file + From a40a6905bec869940c371cc4adeac096ddc4de62 Mon Sep 17 00:00:00 2001 From: keifufu Date: Mon, 31 Mar 2025 13:02:19 +0200 Subject: [PATCH 098/212] newline be gone --- Glamourer/Glamourer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 90c743e..9dabbb2 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -56,4 +56,4 @@ $(GitCommitHash) - + \ No newline at end of file From 95bc52b2bc14758a547d8405a57d31917a42b5c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 2 Apr 2025 23:45:02 +0200 Subject: [PATCH 099/212] Check for valid humanity. --- Glamourer/Interop/Material/MaterialManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index e065e91..ff30483 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -157,7 +157,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Find the type of the given draw object by checking the actors pointers. private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) { - type = MaterialValueIndex.DrawObjectType.Human; + type = MaterialValueIndex.DrawObjectType.Invalid; + if (!((Model)characterBase).IsHuman) + return false; + + type = type = MaterialValueIndex.DrawObjectType.Human; if (!actor.Valid) return false; From 90813ce0300751749a2bba70001be03a4ba338b1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 2 Apr 2025 23:45:28 +0200 Subject: [PATCH 100/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index e717a66..ab63da8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e717a66f33b0656a7c5c971ffa2f63fd96477d94 +Subproject commit ab63da8047f3d99240159bb1b17dbcb61d77326a From b98cb31fd2b03819c283d95ef674ecb7d7f7973c Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 2 Apr 2025 21:47:24 +0000 Subject: [PATCH 101/212] [CI] Updating repo.json for 1.3.8.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index bba0aa8..fae7a8f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.2", - "TestingAssemblyVersion": "1.3.8.2", + "AssemblyVersion": "1.3.8.3", + "TestingAssemblyVersion": "1.3.8.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 096d82741d16e9e88e626c950b83c5a07f94f20c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 3 Apr 2025 01:04:58 +0200 Subject: [PATCH 102/212] Fix previous fix for weapons. --- Glamourer/Interop/Material/MaterialManager.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index ff30483..9eccb29 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -157,19 +157,23 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Find the type of the given draw object by checking the actors pointers. private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) { - type = MaterialValueIndex.DrawObjectType.Invalid; - if (!((Model)characterBase).IsHuman) - return false; - - type = type = MaterialValueIndex.DrawObjectType.Human; if (!actor.Valid) + { + type = MaterialValueIndex.DrawObjectType.Invalid; return false; + } - if (actor.Model.AsCharacterBase == characterBase) + if (actor.Model.AsCharacterBase == characterBase && ((Model)characterBase).IsHuman) + { + type = MaterialValueIndex.DrawObjectType.Human; return true; + } if (!actor.AsObject->IsCharacter()) + { + type = MaterialValueIndex.DrawObjectType.Invalid; return false; + } if (actor.AsCharacter->DrawData.WeaponData[0].DrawObject == characterBase) { @@ -183,6 +187,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable return true; } + type = MaterialValueIndex.DrawObjectType.Invalid; return false; } From 118f51cc647e82ec2910a3678d856e95fba457b3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 2 Apr 2025 23:07:10 +0000 Subject: [PATCH 103/212] [CI] Updating repo.json for 1.3.8.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index fae7a8f..b00ebe5 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.3", - "TestingAssemblyVersion": "1.3.8.3", + "AssemblyVersion": "1.3.8.4", + "TestingAssemblyVersion": "1.3.8.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 46f8818cee7cae30c5ac2d444290b389c85350a9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 4 Apr 2025 22:35:42 +0200 Subject: [PATCH 104/212] Add Incognito Modifier. --- Glamourer/Configuration.cs | 1 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/HeaderDrawer.cs | 28 ++++++++++++++----- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 4 +++ 7 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index ef51544..b1b9ec2 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -75,6 +75,7 @@ public class Configuration : IPluginConfiguration, ISavable public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); + public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control); public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public QdbButtons QdbButtons { get; set; } = diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 0071b1f..0381a17 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -87,7 +87,7 @@ public class ActorPanel _rightButtons = [ new LockedButton(this), - new HeaderDrawer.IncognitoButton(_config.Ephemeral), + new HeaderDrawer.IncognitoButton(_config), ]; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 94329e5..badeaeb 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -31,7 +31,7 @@ public class SetPanel( RandomRestrictionDrawer _randomDrawer) { private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); - private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)]; + private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config)]; private string? _tempName; private int _dragIndex = -1; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 04e51ce..3543b68 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -89,7 +89,7 @@ public class DesignPanel _rightButtons = [ new LockButton(this), - new IncognitoButton(_config.Ephemeral), + new IncognitoButton(_config), ]; } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 2235160..2a5b3e1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -13,7 +13,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; 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 Button[] _rightButtons = [new IncognitoButton(config)]; private readonly DesignColorCombo _colorCombo = new(colors, true); diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index 0e9237d..d6baeb9 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -44,22 +44,36 @@ public static class HeaderDrawer } } - public sealed class IncognitoButton(EphemeralConfig config) : Button + public sealed class IncognitoButton(Configuration config) : Button { protected override string Description - => config.IncognitoMode - ? "Toggle incognito mode off." - : "Toggle incognito mode on."; + { + get + { + var hold = config.IncognitoModifier.IsActive(); + return (config.Ephemeral.IncognitoMode, hold) + switch + { + (true, true) => "Toggle incognito mode off.", + (false, true) => "Toggle incognito mode on.", + (true, false) => $"Toggle incognito mode off.\n\nHold {config.IncognitoModifier} while clicking to toggle.", + (false, false) => $"Toggle incognito mode on.\n\nHold {config.IncognitoModifier} while clicking to toggle.", + }; + } + } protected override FontAwesomeIcon Icon - => config.IncognitoMode + => config.Ephemeral.IncognitoMode ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye; protected override void OnClick() { - config.IncognitoMode = !config.IncognitoMode; - config.Save(); + if (!config.IncognitoModifier.IsActive()) + return; + + config.Ephemeral.IncognitoMode = !config.Ephemeral.IncognitoMode; + config.Ephemeral.Save(); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index d6d2c15..11af9b9 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -188,6 +188,10 @@ public class SettingsTab( "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, config.DeleteDesignModifier, v => config.DeleteDesignModifier = v)) config.Save(); + if (Widget.DoubleModifierSelector("Incognito Modifier", + "A modifier you need to hold while clicking the Incognito button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, + config.IncognitoModifier, v => config.IncognitoModifier = v)) + config.Save(); DrawRenameSettings(); Checkbox("Auto-Open Design Folders"u8, "Have design folders open or closed as their default state after launching."u8, config.OpenFoldersByDefault, From 8fe0ac81952b5650f80207270ec1610e74062333 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 5 Apr 2025 15:12:27 +0200 Subject: [PATCH 105/212] Fix weapon color set issue. --- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 4d1feef..bd60a60 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -119,7 +119,7 @@ public sealed unsafe class PrepareColorSet case MaterialValueIndex.DrawObjectType.Human: return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None; case MaterialValueIndex.DrawObjectType.Mainhand: - var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[0].DrawObject; return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None; case MaterialValueIndex.DrawObjectType.Offhand: var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; From c0ad4aab510689f01ab07028a87b468003e68baa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 5 Apr 2025 18:48:39 +0200 Subject: [PATCH 106/212] Update for new ActorObjectManager. --- Glamourer/Api/ApiHelpers.cs | 12 +- Glamourer/Api/StateApi.cs | 37 ++-- Glamourer/Automation/AutoDesignApplier.cs | 28 ++- Glamourer/Designs/History/EditorHistory.cs | 2 +- Glamourer/Events/StateChanged.cs | 1 + Glamourer/Events/StateFinalized.cs | 1 + Glamourer/Glamourer.cs | 6 +- Glamourer/Gui/DesignQuickBar.cs | 48 ++-- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 16 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 11 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 26 +-- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 14 +- .../DebugTab/AdvancedCustomizationDrawer.cs | 8 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 44 ++-- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 62 +++--- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 61 ++--- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 3 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 10 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 6 +- .../SettingsTab/CollectionOverrideDrawer.cs | 13 +- Glamourer/Interop/ContextMenuService.cs | 15 +- Glamourer/Interop/ObjectManager.cs | 209 ------------------ .../Interop/Penumbra/ModSettingApplier.cs | 3 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 8 +- Glamourer/Interop/Structs/ActorData.cs | 47 ---- Glamourer/Services/CommandService.cs | 46 ++-- Glamourer/Services/DesignApplier.cs | 15 +- Glamourer/Services/ServiceManager.cs | 2 + Glamourer/State/FunModule.cs | 39 ++-- Glamourer/State/StateApplier.cs | 8 +- Glamourer/State/StateEditor.cs | 1 + Glamourer/State/StateListener.cs | 21 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 9 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 38 files changed, 273 insertions(+), 578 deletions(-) delete mode 100644 Glamourer/Interop/ObjectManager.cs delete mode 100644 Glamourer/Interop/Structs/ActorData.cs diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index ed58500..238d349 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -6,12 +6,12 @@ using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.String; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Api; -public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService +public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal IEnumerable FindExistingStates(string actorName) @@ -27,7 +27,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state) { - var actor = objects[objectIndex]; + var actor = objects.Objects[objectIndex]; var identifier = actor.GetIdentifier(actors); if (!identifier.IsValid) { @@ -42,7 +42,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal ActorState? FindState(int objectIndex) { - var actor = objects[objectIndex]; + var actor = objects.Objects[objectIndex]; var identifier = actor.GetIdentifier(actors); if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state)) return state; @@ -73,10 +73,8 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString)) return []; - objects.Update(); - return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString) - .Concat(objects.Identifiers + .Concat(objects .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString) .SelectWhere(kvp => { diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index c27abb7..43dc453 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -4,34 +4,32 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; namespace Glamourer.Api; public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { - private readonly ApiHelpers _helpers; - private readonly StateManager _stateManager; - private readonly DesignConverter _converter; - private readonly Configuration _config; - private readonly AutoDesignApplier _autoDesigns; - private readonly ObjectManager _objects; - private readonly StateChanged _stateChanged; - private readonly StateFinalized _stateFinalized; - private readonly GPoseService _gPose; + private readonly ApiHelpers _helpers; + private readonly StateManager _stateManager; + private readonly DesignConverter _converter; + private readonly Configuration _config; + private readonly AutoDesignApplier _autoDesigns; + private readonly ActorObjectManager _objects; + private readonly StateChanged _stateChanged; + private readonly StateFinalized _stateFinalized; + private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, StateManager stateManager, DesignConverter converter, Configuration config, AutoDesignApplier autoDesigns, - ObjectManager objects, + ActorObjectManager objects, StateChanged stateChanged, StateFinalized stateFinalized, GPoseService gPose) @@ -219,7 +217,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); - RevertToAutomation(_objects[objectIndex], state, key, flags); + RevertToAutomation(_objects.Objects[objectIndex], state, key, flags); return ApiHelpers.Return(GlamourerApiEc.Success, args); } @@ -272,15 +270,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) { - case ApplyFlag.Equipment: - _stateManager.ResetEquip(state, source, key); - break; - case ApplyFlag.Customization: - _stateManager.ResetCustomize(state, source, key); - break; - case ApplyFlag.Equipment | ApplyFlag.Customization: - _stateManager.ResetState(state, source, key); - break; + case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; + case ApplyFlag.Customization: _stateManager.ResetCustomize(state, source, key); break; + case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key); break; } ApiHelpers.Lock(state, key, flags); @@ -288,7 +280,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags) { - _objects.Update(); if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) return GlamourerApiEc.ActorNotFound; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 545dff7..a61a004 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -11,29 +11,28 @@ using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Automation; public sealed class AutoDesignApplier : IDisposable { - private readonly Configuration _config; - private readonly AutoDesignManager _manager; - private readonly StateManager _state; - private readonly JobService _jobs; - private readonly EquippedGearset _equippedGearset; - private readonly ActorManager _actors; - private readonly AutomationChanged _event; - private readonly ObjectManager _objects; - private readonly WeaponLoading _weapons; - private readonly HumanModelList _humans; - private readonly DesignMerger _designMerger; - private readonly IClientState _clientState; + private readonly Configuration _config; + private readonly AutoDesignManager _manager; + private readonly StateManager _state; + private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly ActorObjectManager _objects; + private readonly WeaponLoading _weapons; + private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; + private readonly IClientState _clientState; private readonly JobChangeState _jobChangeState; public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, - AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, + AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) { _config = config; @@ -154,7 +153,6 @@ public sealed class AutoDesignApplier : IDisposable if (newSet is not { Enabled: true }) return; - _objects.Update(); foreach (var id in newSet.Identifiers) { if (_objects.TryGetValue(id, out var data)) diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 6382b94..58bce3d 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -1,8 +1,8 @@ using Glamourer.Api.Enums; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; +using Penumbra.GameData.Interop; namespace Glamourer.Designs.History; diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index c704195..2bcc6fe 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -3,6 +3,7 @@ using Glamourer.Designs.History; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Events/StateFinalized.cs b/Glamourer/Events/StateFinalized.cs index e8548e9..0ccaa8b 100644 --- a/Glamourer/Events/StateFinalized.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -2,6 +2,7 @@ using Glamourer.Api; using Glamourer.Api.Enums; using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9191c4f..1e2a62d 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -6,12 +6,10 @@ using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; -using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Services; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; namespace Glamourer; @@ -82,7 +80,7 @@ public class Glamourer : IDalamudPlugin var designManager = _services.GetService(); var autoManager = _services.GetService(); var stateManager = _services.GetService(); - var objectManager = _services.GetService(); + var objectManager = _services.GetService(); var currentPlayer = objectManager.PlayerData.Identifier; var states = stateManager.Where(kvp => objectManager.ContainsKey(kvp.Key)).ToList(); diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index c9ace0c..5112d97 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -6,14 +6,13 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; namespace Glamourer.Gui; @@ -37,21 +36,21 @@ public sealed class DesignQuickBar : Window, IDisposable ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing; - private readonly Configuration _config; - private readonly QuickDesignCombo _designCombo; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ObjectManager _objects; - private readonly PenumbraService _penumbra; - private readonly IKeyState _keyState; - private readonly ImRaii.Style _windowPadding = new(); - private readonly ImRaii.Color _windowColor = new(); - private DateTime _keyboardToggle = DateTime.UnixEpoch; - private int _numButtons; - private readonly StringBuilder _tooltipBuilder = new(512); + private readonly Configuration _config; + private readonly QuickDesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ActorObjectManager _objects; + private readonly PenumbraService _penumbra; + private readonly IKeyState _keyState; + private readonly ImRaii.Style _windowPadding = new(); + private readonly ImRaii.Color _windowColor = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, - ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) + ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; @@ -222,7 +221,8 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, available); ImGui.SameLine(); @@ -258,9 +258,10 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -300,7 +301,8 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, available); ImGui.SameLine(); @@ -424,7 +426,9 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - _tooltipBuilder.Append("Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + _tooltipBuilder + .Append( + "Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") .Append(_playerIdentifier) .Append('.'); } @@ -434,7 +438,9 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) _tooltipBuilder.Append('\n'); available |= 2; - _tooltipBuilder.Append("Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + _tooltipBuilder + .Append( + "Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") .Append(_targetIdentifier) .Append('.'); } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index cf7e1f3..1723333 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,6 +1,5 @@ using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; @@ -9,18 +8,19 @@ using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui; public sealed class PenumbraChangedItemTooltip : IDisposable { - private readonly PenumbraService _penumbra; - private readonly StateManager _stateManager; - private readonly ItemManager _items; - private readonly ObjectManager _objects; - private readonly CustomizeService _customize; - private readonly GPoseService _gpose; + private readonly PenumbraService _penumbra; + private readonly StateManager _stateManager; + private readonly ItemManager _items; + private readonly ActorObjectManager _objects; + private readonly CustomizeService _customize; + private readonly GPoseService _gpose; private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; @@ -33,7 +33,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public DateTime LastTooltip { get; private set; } = DateTime.MinValue; public DateTime LastClick { get; private set; } = DateTime.MinValue; - public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects, + public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ActorObjectManager objects, CustomizeService customize, GPoseService gpose) { _penumbra = penumbra; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 0381a17..5419f23 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -10,7 +10,6 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -22,7 +21,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.ActorTab; @@ -35,7 +33,7 @@ public class ActorPanel private readonly AutoDesignApplier _autoDesignApplier; private readonly Configuration _config; private readonly DesignConverter _converter; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly DesignManager _designManager; private readonly ImportService _importService; private readonly ICondition _conditions; @@ -53,7 +51,7 @@ public class ActorPanel AutoDesignApplier autoDesignApplier, Configuration config, DesignConverter converter, - ObjectManager objects, + ActorObjectManager objects, DesignManager designManager, ImportService importService, ICondition conditions, @@ -106,7 +104,7 @@ public class ActorPanel { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; - _lockedRedraw = _identifier.Type is IdentifierType.Special + _lockedRedraw = _identifier.Type is IdentifierType.Special || _objects.IsInLobby || _conditions[ConditionFlag.OccupiedInCutSceneEvent]; (_actorName, _actor) = GetHeaderName(); DrawHeader(); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 3269fd2..e46d651 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,7 +1,4 @@ -using System.Security.AccessControl; -using Dalamud.Interface; -using Glamourer.Interop; -using Glamourer.Interop.Structs; +using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -9,11 +6,12 @@ using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) +public class ActorSelector(ActorObjectManager objects, ActorManager actors, EphemeralConfig config) { private ActorIdentifier _identifier = ActorIdentifier.Invalid; @@ -89,11 +87,10 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral if (!child) return; - objects.Update(); _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, + var remainder = ImGuiClip.FilteredClippedDraw(objects.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index badeaeb..7f576b3 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -434,7 +434,7 @@ public class SetPanel( if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) { _dragIndex = index; - _selector._dragDesignIndex = index; + _selector.DragDesignIndex = index; } } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 950b735..09ee6aa 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -2,12 +2,11 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; -using Glamourer.Interop; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; -using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; using Penumbra.String; using ImGuiClip = OtterGui.ImGuiClip; @@ -18,8 +17,7 @@ public class SetSelector : IDisposable private readonly Configuration _config; private readonly AutoDesignManager _manager; private readonly AutomationChanged _event; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly List<(AutoDesignSet, int)> _list = []; public AutoDesignSet? Selection { get; private set; } @@ -38,14 +36,13 @@ public class SetSelector : IDisposable private int _dragIndex = -1; private Action? _endAction; - internal int _dragDesignIndex = -1; + internal int DragDesignIndex = -1; - public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects) + public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorObjectManager objects) { _manager = manager; _event = @event; _config = config; - _actors = actors; _objects = objects; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector); } @@ -94,7 +91,7 @@ public class SetSelector : IDisposable } private LowerString _filter = LowerString.Empty; - private uint _enabledFilter = 0; + private uint _enabledFilter; private float _width; private Vector2 _defaultItemSpacing; private Vector2 _selectableSize; @@ -177,7 +174,6 @@ public class SetSelector : IDisposable UpdateList(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); _selectableSize = new Vector2(0, 2 * ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y); - _objects.Update(); ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); _endAction?.Invoke(); _endAction = null; @@ -186,7 +182,7 @@ public class SetSelector : IDisposable private void DrawSetSelectable((AutoDesignSet Set, int Index) pair) { using var id = ImRaii.PushId(pair.Index); - using (var color = ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value())) + using (ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value())) { if (ImGui.Selectable(GetSetName(pair.Set, pair.Index), pair.Set == Selection, ImGuiSelectableFlags.None, _selectableSize)) { @@ -285,9 +281,9 @@ public class SetSelector : IDisposable private void NewSetButton(Vector2 size) { - var id = _actors.GetCurrentPlayer(); + var id = _objects.Actors.GetCurrentPlayer(); if (!id.IsValid) - id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); + id = _objects.Actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) _manager.AddDesignSet("New Automation Set", id); @@ -332,15 +328,15 @@ public class SetSelector : IDisposable } else if (ImGuiUtil.IsDropping("DesignDragDrop")) { - if (_dragDesignIndex >= 0) + if (DragDesignIndex >= 0) { - var idx = _dragDesignIndex; + var idx = DragDesignIndex; var setTo = set; var setFrom = Selection!; _endAction = () => _manager.MoveDesignToSet(setFrom, idx, setTo); } - _dragDesignIndex = -1; + DragDesignIndex = -1; } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index f5fe088..b4bdc2a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -1,18 +1,17 @@ using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Designs; -using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer +public class ActiveStatePanel(StateManager _stateManager, ActorObjectManager _objectManager) : IGameDataDrawer { public string Label => $"Active Actors ({_stateManager.Count})###Active Actors"; @@ -22,8 +21,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM public void Draw() { - _objectManager.Update(); - foreach (var (identifier, actors) in _objectManager.Identifiers) + foreach (var (identifier, actors) in _objectManager) { if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()), string.Empty, !_stateManager.ContainsKey(identifier), true)) @@ -66,13 +64,15 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM static string ItemString(in DesignData data, EquipSlot slot) { var item = data.Item(slot); - return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + return + $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } static string BonusItemString(in DesignData data, BonusItemFlag slot) { var item = data.BonusItem(slot); - return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + return + $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]); diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs index 6f6d27a..5a02621 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -1,13 +1,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Glamourer.Interop; using ImGuiNET; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDataDrawer +public unsafe class AdvancedCustomizationDrawer(ActorObjectManager objects) : IGameDataDrawer { public string Label => "Advanced Customizations"; @@ -31,8 +31,8 @@ public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDa return; } - DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); - DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); + DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); + DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); DrawCBuffer("Unk1"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA0), 2); DrawCBuffer("Unk2"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA8), 3); } diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 62f93e9..100cc9c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -3,24 +3,25 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Designs; -using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class GlamourPlatePanel : IGameDataDrawer { - private readonly DesignManager _design; - private readonly ItemManager _items; - private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly DesignManager _design; + private readonly ItemManager _items; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; public string Label => "Glamour Plates"; @@ -28,7 +29,8 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer public bool Disabled => false; - public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, ObjectManager objects) + public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, + ActorObjectManager objects) { _items = items; _design = design; @@ -42,24 +44,24 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer var manager = MirageManager.Instance(); using (ImRaii.Group()) { - ImGui.TextUnformatted("Address:"); - ImGui.TextUnformatted("Number of Glamour Plates:"); - ImGui.TextUnformatted("Glamour Plates Requested:"); - ImGui.TextUnformatted("Glamour Plates Loaded:"); - ImGui.TextUnformatted("Is Applying Glamour Plates:"); + ImUtf8.Text("Address:"u8); + ImUtf8.Text("Number of Glamour Plates:"u8); + ImUtf8.Text("Glamour Plates Requested:"u8); + ImUtf8.Text("Glamour Plates Loaded:"u8); + ImUtf8.Text("Is Applying Glamour Plates:"u8); } ImGui.SameLine(); using (ImRaii.Group()) { - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); + ImUtf8.CopyOnClickSelectable($"0x{(ulong)manager:X}"); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); ImGui.SameLine(); - if (ImGui.SmallButton("Request Update")) + if (ImUtf8.SmallButton("Request Update"u8)) RequestGlamour(); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); - ImGui.TextUnformatted(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); } if (manager == null) @@ -71,12 +73,12 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer for (var i = 0; i < manager->GlamourPlates.Length; ++i) { - using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}"); + using var tree = ImUtf8.TreeNode($"Plate #{i + 1:D2}"); if (!tree) continue; ref var plate = ref manager->GlamourPlates[i]; - if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) + if (ImUtf8.ButtonEx("Apply to Player"u8, ""u8, Vector2.Zero, !enabled)) { var design = CreateDesign(plate); _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); @@ -85,14 +87,14 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { foreach (var slot in EquipSlotExtensions.FullSlots) - ImGui.TextUnformatted(slot.ToName()); + ImUtf8.Text(slot.ToName()); } ImGui.SameLine(); using (ImRaii.Group()) { foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) - ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); + ImUtf8.Text($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index c1b5847..fc4799f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -11,12 +11,11 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class ModelEvaluationPanel( - ObjectManager _objectManager, + ActorObjectManager _objectManager, VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, @@ -34,7 +33,7 @@ public unsafe class ModelEvaluationPanel( public void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - var actor = _objectManager[_gameObjectIndex]; + var actor = _objectManager.Objects[_gameObjectIndex]; var model = actor.Model; using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 04537b5..966bc6f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -3,18 +3,18 @@ using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; -using Glamourer.Interop; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) +public class NpcAppearancePanel(NpcCombo npcCombo, StateManager stateManager, ActorObjectManager objectManager, DesignConverter designConverter) : IGameDataDrawer { public string Label @@ -28,9 +28,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM public void Draw() { - ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); + ImUtf8.Checkbox("Compare Customize (or Gear)"u8, ref _customizeOrGear); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); + var resetScroll = ImUtf8.InputText("##npcFilter"u8, ref _npcFilter, "Filter..."u8); using var table = ImRaii.Table("npcs", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); @@ -40,19 +40,19 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM if (resetScroll) ImGui.SetScrollY(0); - ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); - ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Button"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Name"u8, ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); + ImUtf8.TableSetupColumn("Kind"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Id"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Model"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Visor"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Compare"u8, ImGuiTableColumnFlags.WidthStretch); ImGui.TableNextColumn(); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetFrameHeightWithSpacing()); ImGui.TableNextRow(); var idx = 0; - var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, + var remainder = ImGuiClip.FilteredClippedDraw(npcCombo.Items, skips, d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData); ImGui.TableNextColumn(); ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); @@ -61,43 +61,31 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM void DrawData(NpcData data) { using var id = ImRaii.PushId(idx++); - var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); + var disabled = !stateManager.GetOrCreate(objectManager.Player, out var state); ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) + if (ImUtf8.ButtonEx("Apply"u8, ""u8, Vector2.Zero, disabled)) { - foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) - _state.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); - _state.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); - _state.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); + foreach (var (slot, item, stain) in designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) + stateManager.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); + stateManager.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); + stateManager.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); } - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Name); + ImUtf8.DrawFrameColumn(data.Name); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); + ImUtf8.DrawFrameColumn(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Id.Id.ToString()); + ImUtf8.DrawFrameColumn(data.Id.Id.ToString()); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.ModelId.ToString()); + ImUtf8.DrawFrameColumn(data.ModelId.ToString()); using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); + ImUtf8.DrawFrameColumn(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); } using var mono = ImRaii.PushFont(UiBuilder.MonoFont); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); + ImUtf8.DrawFrameColumn(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index e519ea5..a4d1224 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,13 +1,13 @@ -using Glamourer.Interop; -using ImGuiNET; +using ImGuiNET; using OtterGui; -using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Actors; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer +public class ObjectManagerPanel(ActorObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer { public string Label => "Object Manager"; @@ -19,44 +19,45 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto public void Draw() { - _objectManager.Update(); - using (var table = ImRaii.Table("##data", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) + _objectManager.Objects.DrawDebug(); + + using (var table = ImUtf8.Table("##data"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) { if (!table) return; - ImGuiUtil.DrawTableColumn("Last Update"); - ImGuiUtil.DrawTableColumn(_objectManager.LastUpdate.ToString(CultureInfo.InvariantCulture)); + ImUtf8.DrawTableColumn("World"u8); + ImUtf8.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImUtf8.DrawTableColumn(_objectManager.World.ToString()); + + ImUtf8.DrawTableColumn("Player Character"u8); + ImUtf8.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); + ImGui.TableNextColumn(); + ImUtf8.CopyOnClickSelectable(_objectManager.Player.ToString()); + + ImUtf8.DrawTableColumn("In GPose"u8); + ImUtf8.DrawTableColumn(_objectManager.IsInGPose.ToString()); ImGui.TableNextColumn(); - ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); - ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); - - ImGuiUtil.DrawTableColumn("Player Character"); - ImGuiUtil.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.Player.ToString()); - - ImGuiUtil.DrawTableColumn("In GPose"); - ImGuiUtil.DrawTableColumn(_objectManager.IsInGPose.ToString()); + ImUtf8.DrawTableColumn("In Lobby"u8); + ImUtf8.DrawTableColumn(_objectManager.IsInLobby.ToString()); ImGui.TableNextColumn(); if (_objectManager.IsInGPose) { - ImGuiUtil.DrawTableColumn("GPose Player"); - ImGuiUtil.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); + ImUtf8.DrawTableColumn("GPose Player"u8); + ImUtf8.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); + ImUtf8.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); } - ImGuiUtil.DrawTableColumn("Number of Players"); - ImGuiUtil.DrawTableColumn(_objectManager.Count.ToString()); + ImUtf8.DrawTableColumn("Number of Players"u8); + ImUtf8.DrawTableColumn(_objectManager.Count.ToString()); ImGui.TableNextColumn(); } - var filterChanged = ImGui.InputTextWithHint("##Filter", "Filter...", ref _objectFilter, 64); - using var table2 = ImRaii.Table("##data2", 3, + var filterChanged = ImUtf8.InputText("##Filter"u8, ref _objectFilter, "Filter..."u8); + using var table2 = ImUtf8.Table("##data2"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, new Vector2(-1, 20 * ImGui.GetTextLineHeightWithSpacing())); if (!table2) @@ -69,13 +70,13 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_objectManager.Identifiers, skips, + var remainder = ImGuiClip.FilteredClippedDraw(_objectManager, skips, p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p => { - ImGuiUtil.DrawTableColumn(p.Key.ToString()); - ImGuiUtil.DrawTableColumn(p.Value.Label); - ImGuiUtil.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); + ImUtf8.DrawTableColumn(p.Key.ToString()); + ImUtf8.DrawTableColumn(p.Value.Label); + ImUtf8.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); }); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeightWithSpacing()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 2abc1db..21f0c50 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -3,10 +3,11 @@ using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer +public class RetainedStatePanel(StateManager _stateManager, ActorObjectManager _objectManager) : IGameDataDrawer { public string Label => "Retained States (Inactive Actors)"; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3543b68..65014a4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -18,6 +18,7 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; @@ -28,7 +29,7 @@ public class DesignPanel private readonly DesignFileSystemSelector _selector; private readonly CustomizationDrawer _customizationDrawer; private readonly DesignManager _manager; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly StateManager _state; private readonly EquipmentDrawer _equipmentDrawer; private readonly ModAssociationsTab _modAssociations; @@ -48,7 +49,7 @@ public class DesignPanel public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, - ObjectManager objects, + ActorObjectManager objects, StateManager state, EquipmentDrawer equipmentDrawer, ModAssociationsTab modAssociations, @@ -360,6 +361,7 @@ public class DesignPanel equip = false; customize = true; } + if (!enabled) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); @@ -371,6 +373,7 @@ public class DesignPanel _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); } + if (!enabled) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); @@ -386,7 +389,8 @@ public class DesignPanel if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, + equip, equip, equip); if (equip.HasValue) { _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 1151e86..c6166a3 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -5,7 +5,6 @@ using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; -using Glamourer.Interop; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -13,6 +12,7 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.NpcTab; @@ -30,7 +30,7 @@ public class NpcPanel private readonly DesignConverter _converter; private readonly DesignManager _designManager; private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly DesignColors _colors; private readonly Button[] _leftButtons; private readonly Button[] _rightButtons; @@ -42,7 +42,7 @@ public class NpcPanel DesignConverter converter, DesignManager designManager, StateManager state, - ObjectManager objects, + ActorObjectManager objects, DesignColors colors, Configuration config) { diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index d976d28..87490db 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -6,14 +6,14 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Actors; -using ObjectManager = Glamourer.Interop.ObjectManager; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.SettingsTab; public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, Configuration config, - ObjectManager objects, + ActorObjectManager objects, ActorManager actors, PenumbraService penumbra, CollectionCombo combo) : IService @@ -61,7 +61,8 @@ public class CollectionOverrideDrawer( DrawActorIdentifier(idx, actor); ImGui.TableNextColumn(); - if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())) + if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, + ImGui.GetTextLineHeight())) { var (guid, _, newName) = combo.CurrentSelection; collectionOverrides.ChangeOverride(idx, guid, newName); @@ -69,7 +70,7 @@ public class CollectionOverrideDrawer( if (ImGui.IsItemHovered()) { - using var tt = ImRaii.Tooltip(); + using var tt = ImRaii.Tooltip(); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted($" {collection}"); } @@ -102,7 +103,7 @@ public class CollectionOverrideDrawer( return; using var tt2 = ImRaii.Tooltip(); - using var f = ImRaii.PushFont(UiBuilder.MonoFont); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted(collection.ToString()); } @@ -146,7 +147,7 @@ public class CollectionOverrideDrawer( } catch (ActorIdentifierFactory.IdentifierParseError e) { - _exception = e; + _exception = e; _identifiers = []; } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 71a9280..1f85612 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -5,6 +5,7 @@ using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -13,16 +14,16 @@ public class ContextMenuService : IDisposable { public const int ChatLogContextItemId = 0x958; - private readonly ItemManager _items; - private readonly IContextMenu _contextMenu; - private readonly StateManager _state; - private readonly ObjectManager _objects; - private EquipItem _lastItem; - private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; + private readonly ItemManager _items; + private readonly IContextMenu _contextMenu; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; + private EquipItem _lastItem; + private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; private readonly MenuItem _inventoryItem; - public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, Configuration config, + public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration config, IContextMenu context) { _contextMenu = context; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs deleted file mode 100644 index 471a49b..0000000 --- a/Glamourer/Interop/ObjectManager.cs +++ /dev/null @@ -1,209 +0,0 @@ -using Dalamud.Game.ClientState.Objects; -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Game.Control; -using Glamourer.Events; -using Glamourer.Interop.Structs; -using OtterGui.Log; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Interop; - -namespace Glamourer.Interop; - -public class ObjectManager( - IFramework framework, - IClientState clientState, - IObjectTable objects, - IDalamudPluginInterface pi, - Logger log, - ActorManager actors, - ITargetManager targets) - : global::Penumbra.GameData.Interop.ObjectManager(pi, log, framework, objects) -{ - public DateTime LastUpdate - => LastFrame; - - private DateTime _identifierUpdate; - - public bool IsInGPose - => clientState.IsGPosing; - - public ushort World { get; private set; } - - private readonly Dictionary _identifiers = new(200); - private readonly Dictionary _allWorldIdentifiers = new(200); - private readonly Dictionary _nonOwnedIdentifiers = new(200); - - public IReadOnlyDictionary Identifiers - => _identifiers; - - public override bool Update() - { - if (!base.Update() && _identifierUpdate >= LastUpdate) - return false; - - _identifierUpdate = LastUpdate; - World = (ushort)(Player.Valid ? Player.HomeWorld : 0); - _identifiers.Clear(); - _allWorldIdentifiers.Clear(); - _nonOwnedIdentifiers.Clear(); - - foreach (var actor in BattleNpcs.Concat(CutsceneCharacters)) - { - if (actor.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, actor); - } - - void AddSpecial(ScreenActor idx, string label) - { - var actor = this[(int)idx]; - if (actor.Identifier(actors, out var ident)) - { - var data = new ActorData(actor, label); - _identifiers.Add(ident, data); - } - } - - AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor"); - AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor"); - AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor"); - AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor"); - AddSpecial(ScreenActor.Portrait, "Portrait Actor"); - AddSpecial(ScreenActor.Card6, "Card Actor 6"); - AddSpecial(ScreenActor.Card7, "Card Actor 7"); - AddSpecial(ScreenActor.Card8, "Card Actor 8"); - - foreach (var actor in EventNpcs) - { - if (actor.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, actor); - } - - return true; - } - - private void HandleIdentifier(ActorIdentifier identifier, Actor character) - { - if (!identifier.IsValid) - return; - - if (!_identifiers.TryGetValue(identifier, out var data)) - { - data = new ActorData(character, identifier.ToString()); - _identifiers[identifier] = data; - } - else - { - data.Objects.Add(character); - } - - if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) - { - var allWorld = actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, - identifier.Kind, - identifier.DataId); - - if (!_allWorldIdentifiers.TryGetValue(allWorld, out var allWorldData)) - { - allWorldData = new ActorData(character, allWorld.ToString()); - _allWorldIdentifiers[allWorld] = allWorldData; - } - else - { - allWorldData.Objects.Add(character); - } - } - - if (identifier.Type is IdentifierType.Owned) - { - var nonOwned = actors.CreateNpc(identifier.Kind, identifier.DataId); - if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) - { - nonOwnedData = new ActorData(character, nonOwned.ToString()); - _nonOwnedIdentifiers[nonOwned] = nonOwnedData; - } - else - { - nonOwnedData.Objects.Add(character); - } - } - } - - public Actor GPosePlayer - => this[(int)ScreenActor.GPosePlayer]; - - public Actor Player - => this[0]; - - public unsafe Actor Target - => clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; - - public Actor Focus - => targets.FocusTarget?.Address ?? nint.Zero; - - public Actor MouseOver - => targets.MouseOverTarget?.Address ?? nint.Zero; - - public (ActorIdentifier Identifier, ActorData Data) PlayerData - { - get - { - Update(); - return Player.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) - ? (ident, data) - : (ident, ActorData.Invalid); - } - } - - public (ActorIdentifier Identifier, ActorData Data) TargetData - { - get - { - Update(); - return Target.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) - ? (ident, data) - : (ident, ActorData.Invalid); - } - } - - /// Also handles All Worlds players and non-owned NPCs. - public bool ContainsKey(ActorIdentifier key) - => Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key) || _nonOwnedIdentifiers.ContainsKey(key); - - public bool TryGetValue(ActorIdentifier key, out ActorData value) - => Identifiers.TryGetValue(key, out value); - - public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value) - => _allWorldIdentifiers.TryGetValue(key, out value); - - public bool TryGetValueNonOwned(ActorIdentifier key, out ActorData value) - => _nonOwnedIdentifiers.TryGetValue(key, out value); - - public ActorData this[ActorIdentifier key] - => Identifiers[key]; - - public IEnumerable Keys - => Identifiers.Keys; - - public IEnumerable Values - => Identifiers.Values; - - public bool GetName(string lowerName, out Actor actor) - { - (actor, var ret) = lowerName switch - { - "" => (Actor.Null, true), - "" => (Player, true), - "self" => (Player, true), - "" => (Target, true), - "target" => (Target, true), - "" => (Focus, true), - "focus" => (Focus, true), - "" => (MouseOver, true), - "mouseover" => (MouseOver, true), - _ => (Actor.Null, false), - }; - return ret; - } -} diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 1d39f79..b94be09 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; @@ -17,7 +17,6 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; - objects.Update(); if (!objects.TryGetValue(state.Identifier, out var data)) { Glamourer.Log.Verbose( diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 93d23c2..4e3c8e3 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -2,11 +2,11 @@ using Glamourer.Api.Enums; using Glamourer.Designs.History; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; using OtterGui.Services; using Penumbra.Api.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Interop.Penumbra; @@ -16,13 +16,14 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private readonly Configuration _config; private readonly PenumbraService _penumbra; private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly IFramework _framework; private readonly StateChanged _stateChanged; private readonly PenumbraAutoRedrawSkip _skip; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework, + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ActorObjectManager objects, + IFramework framework, StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { _penumbra = penumbra; @@ -78,7 +79,6 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService { _framework.RunOnFrameworkThread(() => { - _objects.Update(); foreach (var (id, state) in _state) { if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) diff --git a/Glamourer/Interop/Structs/ActorData.cs b/Glamourer/Interop/Structs/ActorData.cs deleted file mode 100644 index 5cfbcbe..0000000 --- a/Glamourer/Interop/Structs/ActorData.cs +++ /dev/null @@ -1,47 +0,0 @@ -using OtterGui.Log; -using Penumbra.GameData.Interop; - -namespace Glamourer.Interop.Structs; - -/// -/// A single actor with its label and the list of associated game objects. -/// -public readonly struct ActorData -{ - public readonly List Objects; - public readonly string Label; - - public bool Valid - => Objects.Count > 0; - - public ActorData(Actor actor, string label) - { - Objects = [actor]; - Label = label; - } - - public static readonly ActorData Invalid = new(false); - - private ActorData(bool _) - { - Objects = []; - Label = string.Empty; - } - - public LazyString ToLazyString(string invalid) - { - var objects = Objects; - return Valid - ? new LazyString(() => string.Join(", ", objects.Select(o => o.ToString()))) - : new LazyString(() => invalid); - } - - private ActorData(List objects, string label) - { - Objects = objects; - Label = label; - } - - public ActorData OnlyGPose() - => new(Objects.Where(o => o.IsGPoseOrCutscene).ToList(), Label); -} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fa6e63f..ed51a06 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -17,7 +17,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Services; @@ -26,24 +25,24 @@ public class CommandService : IDisposable, IApiService private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; - private readonly CustomizeService _customizeService; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignResolver _resolver; - private readonly PenumbraService _penumbra; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly CustomizeService _customizeService; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; - public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, + public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, @@ -181,7 +180,9 @@ public class CommandService : IDisposable, IApiService _chat.Print(new SeStringBuilder().AddText("Use with /glamour clearsettings ").AddGreen("[Character Identifier]").AddText(" | ") .AddPurple("").AddText(" | ").AddBlue("").BuiltString); PlayerIdentifierHelp(false, true); - _chat.Print(new SeStringBuilder().AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all").AddText("' to clear all collections.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all") + .AddText("' to clear all collections.").BuiltString); _chat.Print(new SeStringBuilder().AddText(" 》 The booleans are optional and default to 'true', the ").AddPurple("first") .AddText(" determines whether ").AddPurple("manually").AddText(" applied settings are cleared, the ").AddBlue("second") .AddText(" determines whether ").AddBlue("automatically").AddText(" applied settings are cleared.").BuiltString); @@ -372,7 +373,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var data)) @@ -425,7 +425,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var data)) @@ -485,7 +484,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -568,7 +566,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -716,7 +713,6 @@ public class CommandService : IDisposable, IApiService if (!_resolver.GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -794,7 +790,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_stateManager.TryGetValue(identifier, out var state) @@ -835,7 +830,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_stateManager.TryGetValue(identifier, out var state) diff --git a/Glamourer/Services/DesignApplier.cs b/Glamourer/Services/DesignApplier.cs index e0134d4..f0a9ba4 100644 --- a/Glamourer/Services/DesignApplier.cs +++ b/Glamourer/Services/DesignApplier.cs @@ -1,13 +1,12 @@ using Glamourer.Designs; -using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; namespace Glamourer.Services; -public sealed class DesignApplier(StateManager stateManager, ObjectManager objects) : IService +public sealed class DesignApplier(StateManager stateManager, ActorObjectManager objects) : IService { public void ApplyToPlayer(DesignBase design) { @@ -34,16 +33,10 @@ public sealed class DesignApplier(StateManager stateManager, ObjectManager objec } public void Apply(ActorIdentifier actor, DesignBase design) - { - objects.Update(); - Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, ApplySettings.ManualWithLinks); - } + => Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, ApplySettings.ManualWithLinks); public void Apply(ActorIdentifier actor, DesignBase design, ApplySettings settings) - { - objects.Update(); - Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, settings); - } + => Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, settings); public void Apply(ActorIdentifier actor, ActorData data, DesignBase design) => Apply(actor, data, design, ApplySettings.ManualWithLinks); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index baae507..78a0bf0 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -27,6 +27,7 @@ using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Services; @@ -102,6 +103,7 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 1ca5c48..52394a2 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -11,7 +11,6 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; @@ -35,7 +34,7 @@ public unsafe class FunModule : IDisposable private readonly StateManager _stateManager; private readonly DesignConverter _designConverter; private readonly DesignManager _designManager; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly NpcCustomizeSet _npcs; private readonly StainId[] _stains; @@ -69,7 +68,7 @@ public unsafe class FunModule : IDisposable => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, - GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, + GenericPopupWindow popupWindow, StateManager stateManager, ActorObjectManager objects, DesignConverter designConverter, DesignManager designManager, NpcCustomizeSet npcs) { _codes = codes; @@ -125,9 +124,7 @@ public unsafe class FunModule : IDisposable switch (_codes.Masked(CodeService.GearCodes)) { - case CodeService.CodeFlag.Emperor: - SetRandomItem(slot, ref armor); - break; + case CodeService.CodeFlag.Emperor: SetRandomItem(slot, ref armor); break; case CodeService.CodeFlag.Elephants: case CodeService.CodeFlag.Dolphins: case CodeService.CodeFlag.World when actor.Index != 0: @@ -137,9 +134,7 @@ public unsafe class FunModule : IDisposable switch (_codes.Masked(CodeService.DyeCodes)) { - case CodeService.CodeFlag.Clown: - SetRandomDye(ref armor); - break; + case CodeService.CodeFlag.Clown: SetRandomDye(ref armor); break; } } @@ -306,9 +301,7 @@ public unsafe class FunModule : IDisposable SetDolphin(EquipSlot.Body, ref armor[1]); SetDolphin(EquipSlot.Head, ref armor[0]); break; - case CodeService.CodeFlag.World when actor.Index != 0: - _worldSets.Apply(actor, _rng, armor); - break; + case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); break; } switch (_codes.Masked(CodeService.DyeCodes)) @@ -368,17 +361,17 @@ public unsafe class FunModule : IDisposable private static IReadOnlyList DolphinBodies => [ - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6133, 1, new StainIds(4)), // Gaja - new CharacterArmor(6182, 1, new StainIds(3)), // Imp - new CharacterArmor(6182, 1, new StainIds(3)), // Imp - new CharacterArmor(6182, 1, new StainIds(4)), // Imp - new CharacterArmor(6182, 1, new StainIds(4)), // Imp + new(6089, 1, new StainIds(4)), // Toad + new(6089, 1, new StainIds(4)), // Toad + new(6089, 1, new StainIds(4)), // Toad + new(6023, 1, new StainIds(4)), // Swine + new(6023, 1, new StainIds(4)), // Swine + new(6023, 1, new StainIds(4)), // Swine + new(6133, 1, new StainIds(4)), // Gaja + new(6182, 1, new StainIds(3)), // Imp + new(6182, 1, new StainIds(3)), // Imp + new(6182, 1, new StainIds(4)), // Imp + new(6182, 1, new StainIds(4)), // Imp ]; private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index be85580..698151f 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -7,6 +7,7 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -23,7 +24,7 @@ public class StateApplier( ItemManager _items, PenumbraService _penumbra, MetaService _metaService, - ObjectManager _objects, + ActorObjectManager _objects, CrestService _crests, DirectXService _directX) { @@ -411,8 +412,5 @@ public class StateApplier( } private ActorData GetData(ActorState state) - { - _objects.Update(); - return _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; - } + => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 1fa1ffe..986bdc2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -9,6 +9,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.State; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 88ec0b5..24f641c 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -15,7 +15,6 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using Glamourer.Api.Enums; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; @@ -28,7 +27,7 @@ public class StateListener : IDisposable { private readonly Configuration _config; private readonly ActorManager _actors; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; @@ -61,7 +60,7 @@ public class StateListener : IDisposable public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, - FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, + FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) { @@ -205,9 +204,7 @@ public class StateListener : IDisposable } break; - case UpdateState.NoChange: - customize = state.ModelData.Customize; - break; + case UpdateState.NoChange: customize = state.ModelData.Customize; break; } } @@ -262,9 +259,7 @@ public class StateListener : IDisposable item = state.ModelData.BonusItem(slot).Armor(); break; // Use current model data. - case UpdateState.NoChange: - item = state.ModelData.BonusItem(slot).Armor(); - break; + case UpdateState.NoChange: item = state.ModelData.BonusItem(slot).Armor(); break; case UpdateState.Transformed: break; } } @@ -279,7 +274,6 @@ public class StateListener : IDisposable if (!actor.Identifier(_actors, out var identifier)) return; - _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); } @@ -287,7 +281,6 @@ public class StateListener : IDisposable private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { - _objects.Update(); var (identifier, objects) = _objects.PlayerData; if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var state)) return; @@ -310,7 +303,7 @@ public class StateListener : IDisposable var stainChanged = current.Stains == changed.Stains && !state.Sources[slot, true].IsFixed(); - switch ((itemChanged, stainChanged)) + switch (itemChanged, stainChanged) { case (true, true): _manager.ChangeEquip(state, slot, currentItem, current.Stains, ApplySettings.Game); @@ -376,9 +369,7 @@ public class StateListener : IDisposable else apply = true; break; - case UpdateState.NoChange: - apply = true; - break; + case UpdateState.NoChange: apply = true; break; } var baseType = slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index b58385e..bd13f99 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -1,16 +1,15 @@ using Dalamud.Game; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.GameData; using Glamourer.Events; -using Glamourer.Interop; using Glamourer.Services; using Lumina.Excel.Sheets; using Penumbra.GameData; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Unlocks; @@ -19,7 +18,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable private readonly SaveService _saveService; private readonly IClientState _clientState; private readonly ObjectUnlocked _event; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly Dictionary _unlocked = new(); public readonly IReadOnlyDictionary Unlockable; @@ -28,7 +27,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => _unlocked; public CustomizeUnlockManager(SaveService saveService, CustomizeService customizations, IDataManager gameData, - IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop, ObjectManager objects) + IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop, ActorObjectManager objects) { interop.InitializeFromAttributes(this); _saveService = saveService; @@ -177,7 +176,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable IDataManager gameData) { var ret = new Dictionary(); - var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; + var sheet = gameData.GetExcelSheet(ClientLanguage.English); foreach (var (clan, gender) in CustomizeManager.AllSets()) { var list = customizations.Manager.GetSet(clan, gender); diff --git a/OtterGui b/OtterGui index 3396ee1..f53fd22 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3396ee176fa72ad2dfb2de3294f7125ebce4dae5 +Subproject commit f53fd227a242435ce44a9fe9c5e847d0ca788869 diff --git a/Penumbra.GameData b/Penumbra.GameData index ab63da8..801e98a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ab63da8047f3d99240159bb1b17dbcb61d77326a +Subproject commit 801e98a2956d707a25fd19bdfa46dc674c95365d From aad978f5f68bb36eb43f78f18c45ce7160c993e3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 8 Apr 2025 22:53:59 +0200 Subject: [PATCH 107/212] Pass actor in GearsetDataLoaded instead of looking it up. --- Glamourer/Events/GearsetDataLoaded.cs | 2 +- Glamourer/Interop/UpdateSlotService.cs | 2 +- Glamourer/State/StateListener.cs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 4750939..620bdab 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -11,7 +11,7 @@ namespace Glamourer.Events; /// /// public sealed class GearsetDataLoaded() - : EventWrapper(nameof(GearsetDataLoaded)) + : EventWrapper(nameof(GearsetDataLoaded)) { public enum Priority { diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index ffa6581..3ef99d9 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -120,7 +120,7 @@ public unsafe class UpdateSlotService : IDisposable { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); var drawObject = drawDataContainer->OwnerObject->DrawObject; - GearsetDataLoadedEvent.Invoke(drawObject); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject, drawObject); Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 24f641c..90520b2 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -264,10 +264,9 @@ public class StateListener : IDisposable } } - private void OnGearsetDataLoaded(Model model) + private void OnGearsetDataLoaded(Actor actor, Model model) { - var actor = _penumbra.GameObjectFromDrawObject(model); - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (!actor.Valid || (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)) return; // ensure actor and state are valid. From 7ed42005dd8e8a2563a6fcaf4820a6a325a7943f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 9 Apr 2025 15:11:04 +0200 Subject: [PATCH 108/212] Force higher Penumbra API version and use better IPC for cutscene parents and game objects. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 94 ++++++++----------- Penumbra.Api | 2 +- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index e04af7c..3f93551 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -34,12 +34,8 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 3; - public const int RequiredPenumbraFeatureVersionTemp = 4; - public const int RequiredPenumbraFeatureVersionTemp2 = 5; - public const int RequiredPenumbraFeatureVersionTemp3 = 6; - public const int RequiredPenumbraFeatureVersionTemp4 = 7; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 8; private const int KeyFixed = -1610; private const string NameFixed = "Glamourer (Automation)"; @@ -57,8 +53,6 @@ public class PenumbraService : IDisposable 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; @@ -82,6 +76,8 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; private Func? _checkCurrentChangedItems; + private Func? _checkCutsceneParent; + private Func? _getGameObject; private readonly IDisposable _initializedEvent; private readonly IDisposable _disposedEvent; @@ -453,11 +449,11 @@ public class PenumbraService : IDisposable /// Obtain the game object corresponding to a draw object. public Actor GameObjectFromDrawObject(Model drawObject) - => Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : Actor.Null; + => _getGameObject?.Invoke(drawObject.Address) ?? Actor.Null; /// Obtain the parent of a cutscene actor if it is known. public short CutsceneParent(ushort idx) - => (short)(Available ? _cutsceneParent!.Invoke(idx) : -1); + => (short)(_checkCutsceneParent?.Invoke(idx) ?? -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) @@ -519,49 +515,37 @@ public class PenumbraService : IDisposable _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); _modSettingChanged.Enable(); - _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); - _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); - _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); - _drawObjectInfo = new global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo(_pluginInterface); - _cutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex(_pluginInterface); - _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); - _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); - _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); - _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); - _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); - _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); - _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); - _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); - _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); - _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); - _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); - _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); - _removeAllTemporaryModSettingsPlayer = - new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) - { - _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); - _queryTemporaryModSettingsPlayer = - new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); - 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(); - } - } - } - } + _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); + _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); + _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); + _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); + _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); + _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); + _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); + _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); + _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); + _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); + _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); + _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); + _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); + _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); + _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); + _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); + _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); + _removeAllTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); + _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + _queryTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); + _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); + _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); + _checkCurrentChangedItems = + new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); Available = true; _penumbraReloaded.Invoke(); @@ -587,8 +571,8 @@ public class PenumbraService : IDisposable _collectionByIdentifier = null; _collections = null; _redraw = null; - _drawObjectInfo = null; - _cutsceneParent = null; + _getGameObject = null; + _checkCutsceneParent = null; _objectCollection = null; _getMods = null; _currentCollection = null; diff --git a/Penumbra.Api b/Penumbra.Api index bd56d82..47bd542 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit bd56d82816b8366e19dddfb2dc7fd7f167e264ee +Subproject commit 47bd5424d04c667d0df1ac1dd1eeb3e50b476c2c From 6c556d6a610e05d3900b4202c8e733d92a25747c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 10 Apr 2025 16:20:15 +0200 Subject: [PATCH 109/212] Update API. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 47bd542..f578091 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 47bd5424d04c667d0df1ac1dd1eeb3e50b476c2c +Subproject commit f578091fa579fb098c21036b492ff6e6088184c9 From bfce99859ff7540ddd52d00723015645534f6660 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 10 Apr 2025 16:39:04 +0200 Subject: [PATCH 110/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 801e98a..e10d8f3 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 801e98a2956d707a25fd19bdfa46dc674c95365d +Subproject commit e10d8f33a676ff4544d7ca05a93d555416f41222 From 4f6fb44f79ef88b2cedffada8a0cf6231e52bbb3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 10 Apr 2025 14:42:24 +0000 Subject: [PATCH 111/212] [CI] Updating repo.json for 1.3.8.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index b00ebe5..3e8e6dc 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.4", - "TestingAssemblyVersion": "1.3.8.4", + "AssemblyVersion": "1.3.8.5", + "TestingAssemblyVersion": "1.3.8.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 155a9d62660f57a6c7b7156199be4c055976df49 Mon Sep 17 00:00:00 2001 From: Caraxi Date: Tue, 15 Apr 2025 14:26:30 +0930 Subject: [PATCH 112/212] Fix `SetMetaState` and `SetMetaStateName` not being registered --- Glamourer/Api/IpcProviders.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 704f008..bd4da53 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -36,6 +36,8 @@ public sealed class IpcProviders : IDisposable, IApiService (a, b, c, d, e, f) => (int)api.Items.SetItemName(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), IpcSubscribers.SetBonusItem.Provider(pi, api.Items), IpcSubscribers.SetBonusItemName.Provider(pi, api.Items), + IpcSubscribers.SetMetaState.Provider(pi, api.Items), + IpcSubscribers.SetMetaStateName.Provider(pi, api.Items), IpcSubscribers.GetState.Provider(pi, api.State), IpcSubscribers.GetStateName.Provider(pi, api.State), IpcSubscribers.GetStateBase64.Provider(pi, api.State), From 325b54031c431616dcadb09969b8fa39e6043dfc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 19 Apr 2025 22:28:00 +0200 Subject: [PATCH 113/212] Update libraries. --- Glamourer/Api/ApiHelpers.cs | 1 + Glamourer/Automation/AutoDesignManager.cs | 1 + Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignBase64Migration.cs | 1 + Glamourer/Designs/DesignColors.cs | 1 + Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Designs/Links/DesignLinkLoader.cs | 2 +- Glamourer/GameData/CustomizeSet.cs | 1 + Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs | 1 + Glamourer/Gui/DesignCombo.cs | 1 + Glamourer/Gui/Equipment/BonusItemCombo.cs | 1 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 1 + Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs | 1 + Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 1 + Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 1 + Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 1 + Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 1 + Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 1 + Glamourer/Interop/Material/UpdateColorSets.cs | 4 ++-- Glamourer/Services/CollectionOverrideService.cs | 1 + Glamourer/Services/CommandService.cs | 1 + Glamourer/State/FunModule.cs | 1 + OtterGui | 2 +- Penumbra.GameData | 2 +- 31 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 238d349..45d84b9 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using Glamourer.State; using OtterGui; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 6635a89..7a4511b 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Filesystem; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index b1b9ec2..4339186 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Newtonsoft.Json; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Widgets; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index cab9e27..8cd137f 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,6 +1,7 @@ using Glamourer.Api.Enums; using Glamourer.Services; using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 96592bf..172e10f 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index ef01e98..fff9565 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,7 +8,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs index fc7a26d..24138a8 100644 --- a/Glamourer/Designs/Links/DesignLinkLoader.cs +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.ImGuiNotification; -using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Services; using Notification = OtterGui.Classes.Notification; diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index d0193aa..8795c19 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,4 +1,5 @@ using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 486fdb4..eabb6f0 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -3,6 +3,7 @@ using Glamourer.GameData; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index dccfa44..a871cf1 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index c333a87..892d9f1 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -5,6 +5,7 @@ using ImGuiNET; using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 2bd84e7..0d2e8dc 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.EndObjects; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index f7c75e1..14f2e8a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -3,8 +3,8 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using Lumina.Excel.Sheets; -using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 37d9d3c..e96f721 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -3,6 +3,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 530e04a..8a08437 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -3,6 +3,7 @@ using Dalamud.Utility; using ImGuiNET; using OtterGui; using OtterGui.Custom; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 7f576b3..1600837 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 09ee6aa..96730e8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -5,6 +5,7 @@ using Glamourer.Events; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Interop; using Penumbra.String; diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index 98b7d9e..df39f45 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -1,6 +1,7 @@ using Glamourer.Automation; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index b562ecf..ede3e9e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index c893f4c..26be8ce 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -3,6 +3,7 @@ using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 100cc9c..aafc21a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -7,6 +7,7 @@ using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index e59be09..9444ff7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,6 +1,7 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 80000d9..bab25f0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -8,6 +8,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 2a5b3e1..3567fb6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -4,6 +4,7 @@ using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index b3f0cef..847b65e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,6 +1,7 @@ using Glamourer.GameData; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using ImGuiClip = OtterGui.ImGuiClip; diff --git a/Glamourer/Interop/Material/UpdateColorSets.cs b/Glamourer/Interop/Material/UpdateColorSets.cs index a96c60f..e503bc6 100644 --- a/Glamourer/Interop/Material/UpdateColorSets.cs +++ b/Glamourer/Interop/Material/UpdateColorSets.cs @@ -8,7 +8,7 @@ public sealed class UpdateColorSets : FastHook { public delegate void Delegate(Model model, uint unk); - private readonly ThreadLocal _updatingModel = new(); + private readonly ThreadLocal _updatingModel = new(() => Model.Null); public UpdateColorSets(HookManager hooks) => Task = hooks.CreateHook("Update Color Sets", Sigs.UpdateColorSets, Detour, true); @@ -17,7 +17,7 @@ public sealed class UpdateColorSets : FastHook { _updatingModel.Value = model; Task.Result.Original(model, unk); - _updatingModel.Value = nint.Zero; + _updatingModel.Value = Model.Null; } public Model Get() diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index fcc9998..99635d8 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -3,6 +3,7 @@ using Glamourer.Interop.Penumbra; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; +using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index ed51a06..3d40fa9 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -12,6 +12,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 52394a2..ae7160c 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -7,6 +7,7 @@ using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; diff --git a/OtterGui b/OtterGui index f53fd22..d13d700 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f53fd227a242435ce44a9fe9c5e847d0ca788869 +Subproject commit d13d700796648f2a9279250052c4aa8ebeca221f diff --git a/Penumbra.GameData b/Penumbra.GameData index e10d8f3..62bbce5 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e10d8f33a676ff4544d7ca05a93d555416f41222 +Subproject commit 62bbce5981e961a91322ca1a7d3bb5be25f67185 From c7d1620c1e2f3024c5bb80330ca4681da255c4f6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 19 Apr 2025 23:09:48 +0200 Subject: [PATCH 114/212] Update GameData. --- Glamourer/Services/DalamudServices.cs | 3 +++ Penumbra.GameData | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 02df634..85783b9 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -6,6 +6,8 @@ using OtterGui.Services; namespace Glamourer.Services; +#pragma warning disable SeStringEvaluator + public class DalamudServices { public static void AddServices(ServiceManager services, IDalamudPluginInterface pi) @@ -28,5 +30,6 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); } } diff --git a/Penumbra.GameData b/Penumbra.GameData index 62bbce5..002260d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 62bbce5981e961a91322ca1a7d3bb5be25f67185 +Subproject commit 002260d9815e571f1496c50374f5b712818e9880 From 9a684c9ff5a89e339b529d704be8ed0c2a2351c2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 29 Apr 2025 23:50:29 +0200 Subject: [PATCH 115/212] Implement GetDesignListExtended. --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 7 ++++++- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 5e5c867..2158bd4 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 5e5c867a095eecac0dd494b30a33298a65e46426 +Subproject commit 2158bd4bbcb6cefe3ce48e6d8b32e134cbee9a91 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 08f5ddc..0ee0b64 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -6,11 +6,16 @@ using OtterGui.Services; namespace Glamourer.Api; -public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager) : IGlamourerApiDesigns, IApiService +public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager, DesignFileSystem fileSystem, DesignColors color) + : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + public Dictionary GetDesignListExtended() + => designs.Designs.ToDictionary(d => d.Identifier, + d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) { var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 62107a9..9aaf72f 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 4; + public const int CurrentApiVersionMinor = 5; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 704f008..70ba47f 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -24,6 +24,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ApiVersion.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), + IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), From b53124e7084c1036675c87ab0c922fdba4a63bb7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 1 May 2025 23:02:08 +0200 Subject: [PATCH 116/212] Temporarily use custom address for ReadStainingTemplate. --- Glamourer/Interop/Material/PrepareColorSet.cs | 14 +++++++++----- OtterGui | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index bd60a60..dfa6811 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,4 +1,5 @@ using Dalamud.Hooking; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; @@ -27,7 +28,8 @@ public sealed unsafe class PrepareColorSet : base("Prepare Color Set ") { _updateColorSets = updateColorSets; - _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + hooks.Provider.InitializeFromAttributes(this); + _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); } private readonly Task> _task; @@ -49,6 +51,10 @@ public sealed unsafe class PrepareColorSet private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2); + // TODO use CS when stabilized in Dalamud. + [Signature("E8 ?? ?? ?? ?? 48 8B FB EB 07")] + private static delegate* unmanaged _readStainingTemplate = null; + private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2) { Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}."); @@ -78,12 +84,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers - .ReadStainingTemplate)(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); + _readStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); if (stainIds.Stain2.Id != 0) - ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers - .ReadStainingTemplate)(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); + _readStainingTemplate(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); } table = newTable; diff --git a/OtterGui b/OtterGui index d13d700..abfce28 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d13d700796648f2a9279250052c4aa8ebeca221f +Subproject commit abfce28e0bacdea841f029f59bc19971c43814d8 From 39636f5293d76d42c6b3b84b82bbb73a08826ec4 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 1 May 2025 21:04:23 +0000 Subject: [PATCH 117/212] [CI] Updating repo.json for 1.3.8.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 3e8e6dc..3f1f29b 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.5", - "TestingAssemblyVersion": "1.3.8.5", + "AssemblyVersion": "1.3.8.6", + "TestingAssemblyVersion": "1.3.8.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a6073e2a4214cac6142b0e794a8cb6966120bb90 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 3 May 2025 23:36:03 +0200 Subject: [PATCH 118/212] Update old PR slightly. --- .../Designs/Special/RandomDesignGenerator.cs | 41 +++++++++++-------- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 8 ++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index b34a8ba..b1e1e7c 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -5,22 +5,29 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { - private readonly Random _rng = new(); - private WeakReference _lastDesign = new(null, false); + private readonly Random _rng = new(); + private readonly WeakReference _lastDesign = new(null!, false); public Design? Design(IReadOnlyList localDesigns) { - if (localDesigns.Count == 0) + if (localDesigns.Count is 0) return null; - int idx; - do - idx = _rng.Next(0, localDesigns.Count); - while (config.PreventRandomRepeats && localDesigns.Count > 1 && _lastDesign.TryGetTarget(out var lastDesign) && lastDesign == localDesigns[idx]); - - Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesign.SetTarget(localDesigns[idx]); - return localDesigns[idx]; + var idx = _rng.Next(0, localDesigns.Count); + if (localDesigns.Count is 1) + { + _lastDesign.SetTarget(localDesigns[idx]); + return localDesigns[idx]; + } + + if (config.PreventRandomRepeats && _lastDesign.TryGetTarget(out var lastDesign)) + while (lastDesign == localDesigns[idx]) + idx = _rng.Next(0, localDesigns.Count); + + var design = localDesigns[idx]; + Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {design.Incognito}."); + _lastDesign.SetTarget(design); + return design; } public Design? Design() @@ -31,12 +38,12 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS public Design? Design(IReadOnlyList predicates) { - if (predicates.Count == 0) - return Design(); - if (predicates.Count == 1) - return Design(predicates[0]); - - return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()); + return predicates.Count switch + { + 0 => Design(), + 1 => Design(predicates[0]), + _ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()), + }; } public Design? Design(string restrictions) diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index a16c29e..746bb47 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -102,8 +102,8 @@ public class SettingsTab( "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); - Checkbox("Prevent Random Design Repeats", - "When using random designs, prevent the same design from being chosen twice in a row.", + Checkbox("Prevent Random Design Repeats"u8, + "When using random designs, prevent the same design from being chosen twice in a row."u8, config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); ImGui.NewLine(); } @@ -250,8 +250,8 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { - var showAuto = config.EnableAutoDesigns; - var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var showAuto = config.EnableAutoDesigns; + var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); From fcb0660deffa383d040567350bbab87aad777cf6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 May 2025 00:36:39 +0200 Subject: [PATCH 119/212] Implement new IPC methods and API 1.6 --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 66 ++++++++++++++++++- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 5 ++ .../DebugTab/IpcTester/DesignIpcTester.cs | 45 +++++++++++++ OtterGui | 2 +- 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 2158bd4..9c86a9d 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 2158bd4bbcb6cefe3ce48e6d8b32e134cbee9a91 +Subproject commit 9c86a9d6847f68e75679fe57d34f53f680d949c6 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 0ee0b64..e21e6cb 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -2,11 +2,18 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; +using Newtonsoft.Json.Linq; using OtterGui.Services; namespace Glamourer.Api; -public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager, DesignFileSystem fileSystem, DesignColors color) +public class DesignsApi( + ApiHelpers helpers, + DesignManager designs, + StateManager stateManager, + DesignFileSystem fileSystem, + DesignColors color, + DesignConverter converter) : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() @@ -16,6 +23,11 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager => designs.Designs.ToDictionary(d => d.Identifier, d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } d + ? (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) + : (string.Empty, string.Empty, 0, false); + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) { var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); @@ -71,4 +83,56 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } + + public (GlamourerApiEc, Guid) AddDesign(string designInput, string name) + { + var args = ApiHelpers.Args("DesignData", designInput, "Name", name); + + if (converter.FromBase64(designInput, true, true, out _) is not { } designBase) + try + { + var jObj = JObject.Parse(designInput); + designBase = converter.FromJObject(jObj, true, true); + if (designBase is null) + return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Failure parsing data for AddDesign due to\n{ex}"); + return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty); + } + + try + { + var design = designBase is Design d + ? designs.CreateClone(d, name, true) + : designs.CreateClone(designBase, name, true); + return (ApiHelpers.Return(GlamourerApiEc.Success, args), design.Identifier); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Unknown error creating design via IPC:\n{ex}"); + return (ApiHelpers.Return(GlamourerApiEc.UnknownError, args), Guid.Empty); + } + } + + public GlamourerApiEc DeleteDesign(Guid designId) + { + var args = ApiHelpers.Args("DesignId", designId); + if (designs.Designs.ByIdentifier(designId) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + designs.Delete(design); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public string? GetDesignBase64(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } design + ? converter.ShareBase64(design) + : null; + + public JObject? GetDesignJObject(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } design + ? converter.ShareJObject(design) + : null; } diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 9aaf72f..14c0512 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 5; + public const int CurrentApiVersionMinor = 6; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 8058818..2701f18 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -25,8 +25,13 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetDesignList.Provider(pi, api.Designs), IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), + IpcSubscribers.GetExtendedDesignData.Provider(pi, api.Designs), IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), + IpcSubscribers.AddDesign.Provider(pi, api.Designs), + IpcSubscribers.DeleteDesign.Provider(pi, api.Designs), + IpcSubscribers.GetDesignBase64.Provider(pi, api.Designs), + IpcSubscribers.GetDesignJObject.Provider(pi, api.Designs), IpcSubscribers.SetItem.Provider(pi, api.Items), IpcSubscribers.SetItemName.Provider(pi, api.Items), diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 918c7ad..9e11b99 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -7,6 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; @@ -15,6 +16,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi private Dictionary _designs = []; private int _gameObjectIndex; private string _gameObjectName = string.Empty; + private string _designName = string.Empty; private uint _key; private ApplyFlag _flags = ApplyFlagEx.DesignDefault; private Guid? _design; @@ -30,6 +32,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi IpcTesterHelpers.IndexInput(ref _gameObjectIndex); IpcTesterHelpers.KeyInput(ref _key); IpcTesterHelpers.NameInput(ref _gameObjectName); + ImUtf8.InputText("##designName"u8, ref _designName, "Design Name..."u8); ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText, ImGui.GetContentRegionAvail().X); IpcTesterHelpers.DrawFlagInput(ref _flags); @@ -54,6 +57,48 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi IpcTesterHelpers.DrawIntro(ApplyDesignName.Label); if (ImGuiUtil.DrawDisabledButton("Apply##Name", Vector2.Zero, string.Empty, !_design.HasValue)) _lastError = new ApplyDesignName(pluginInterface).Invoke(_design!.Value, _gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(GetExtendedDesignData.Label); + if (_design.HasValue) + { + var (display, path, color, draw) = new GetExtendedDesignData(pluginInterface).Invoke(_design.Value); + if (path.Length > 0) + ImUtf8.Text($"{display} ({path}){(draw ? " in QDB"u8 : ""u8)}", color); + else + ImUtf8.Text("No Data"u8); + } + else + { + ImUtf8.Text("No Data"u8); + } + + IpcTesterHelpers.DrawIntro(GetDesignBase64.Label); + if (ImUtf8.Button("To Clipboard##Base64"u8) && _design.HasValue) + { + var data = new GetDesignBase64(pluginInterface).Invoke(_design.Value); + ImUtf8.SetClipboardText(data); + } + + IpcTesterHelpers.DrawIntro(AddDesign.Label); + if (ImUtf8.Button("Add from Clipboard"u8)) + try + { + var data = ImUtf8.GetClipboardText(); + _lastError = new AddDesign(pluginInterface).Invoke(data, _designName, out var newDesign); + if (_lastError is GlamourerApiEc.Success) + { + _design = newDesign; + _designText = newDesign.ToString(); + } + } + catch + { + _lastError = GlamourerApiEc.UnknownError; + } + + IpcTesterHelpers.DrawIntro(DeleteDesign.Label); + if (ImUtf8.Button("Delete##Design"u8) && _design.HasValue) + _lastError = new DeleteDesign(pluginInterface).Invoke(_design.Value); } private void DrawDesignsPopup() diff --git a/OtterGui b/OtterGui index abfce28..d1ba194 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit abfce28e0bacdea841f029f59bc19971c43814d8 +Subproject commit d1ba1942efaae219b06ebc27d43de6d1889af97d From b1abbb8e77b0987c0432cc6eac9e5490d9c2b666 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:30:27 +0200 Subject: [PATCH 120/212] Support model id input in weapon combo, support middle-mouse pipette. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 3 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 33 +++++++-- Glamourer/Gui/Equipment/ItemCopyService.cs | 78 ++++++++++++++++++++++ Glamourer/Gui/Equipment/WeaponCombo.cs | 28 +++++++- Penumbra.GameData | 2 +- 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 Glamourer/Gui/Equipment/ItemCopyService.cs diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 77f4533..f32e22b 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -27,6 +27,9 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetStains(StainIds stains) => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual); + public readonly void SetStain(int which, StainId stain) + => _editor.ChangeStains(_object, Slot, CurrentStains.With(which, stain), ApplySettings.Manual); + public readonly void SetApplyItem(bool value) { var manager = (DesignManager)_editor; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0d2e8dc..f2ecc08 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,16 +3,15 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; using Glamourer.Gui.Materials; -using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.EndObjects; using OtterGui.Widgets; +using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -33,6 +32,7 @@ public class EquipmentDrawer private readonly Configuration _config; private readonly GPoseService _gPose; private readonly AdvancedDyePopup _advancedDyes; + private readonly ItemCopyService _itemCopy; private float _requiredComboWidthUnscaled; private float _requiredComboWidth; @@ -40,13 +40,14 @@ public class EquipmentDrawer private Stain? _draggedStain; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, - Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) + Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) { _items = items; _textures = textures; _config = config; _gPose = gPose; _advancedDyes = advancedDyes; + _itemCopy = itemCopy; _stainData = items.Stains; _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); @@ -184,6 +185,7 @@ public class EquipmentDrawer return change; } + #region Small private void DrawEquipSmall(in EquipDrawData equipDrawData) @@ -402,6 +404,7 @@ public class EquipmentDrawer ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + _itemCopy.HandleCopyPaste(data, index); if (!change) DrawStainDragDrop(data, index, stain, found); @@ -456,6 +459,7 @@ public class EquipmentDrawer data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(data); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), out var item)) @@ -473,6 +477,14 @@ public class EquipmentDrawer var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); + if (ImGui.IsItemHovered() && ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsKeyPressed(ImGuiKey.C)) + _itemCopy.Copy(combo.CurrentSelection); + else if (ImGui.IsKeyPressed(ImGuiKey.V)) + _itemCopy.Paste(data.Slot.ToEquipType(), data.SetItem); + } + if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) @@ -531,8 +543,12 @@ public class EquipmentDrawer if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) changedItem = combo.CurrentSelection; - else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, - default, out var c)) + else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) + changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); + _itemCopy.HandleCopyPaste(mainhand); + + if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, + default, out var c)) changedItem = c; if (changedItem != null) @@ -549,7 +565,8 @@ public class EquipmentDrawer } if (unknown) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) @@ -569,6 +586,9 @@ public class EquipmentDrawer if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) offhand.SetItem(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) + offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(offhand); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) @@ -623,6 +643,7 @@ public class EquipmentDrawer { ImUtf8.Text(label); } + if (hasAdvancedDyes) ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8); } diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs new file mode 100644 index 0000000..e72a54b --- /dev/null +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -0,0 +1,78 @@ +using Dalamud.Game.ClientState.Keys; +using Dalamud.Plugin.Services; +using Glamourer.Services; +using ImGuiNET; +using OtterGui.OtterGuiInternal.Enums; +using OtterGui.Services; +using OtterGuiInternal; +using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public class ItemCopyService(ItemManager items, IKeyState keyState, DictStain stainData) : IUiService +{ + public EquipItem? Item { get; private set; } + public Stain? Stain { get; private set; } + + public void Copy(in EquipItem item) + => Item = item; + + public void Copy(in Stain stain) + => Stain = stain; + + public void Paste(int which, Action setter) + { + if (Stain is { } stain) + setter(which, stain.RowIndex); + } + + public void Paste(FullEquipType type, Action setter) + { + if (Item is not { } item) + return; + + if (type != item.Type) + { + if (type.IsBonus()) + item = items.Identify(type.ToBonus(), item.PrimaryId, item.Variant); + else if (type.IsEquipment() || type.IsAccessory()) + item = items.Identify(type.ToSlot(), item.PrimaryId, item.Variant); + else + item = items.Identify(type.ToSlot(), item.PrimaryId, item.SecondaryId, item.Variant); + } + + if (item.Valid && item.Type == type) + setter(item); + } + + public void HandleCopyPaste(in EquipDrawData data) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(data.CurrentItem.Type, data.SetItem); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + { + Copy(data.CurrentItem); + } + } + + public void HandleCopyPaste(in EquipDrawData data, int which) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(which, data.SetStain); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) + && ImGui.IsMouseClicked(ImGuiMouseButton.Middle) + && stainData.TryGetValue(data.CurrentStains[which].Id, out var stain)) + { + Copy(stain); + } + } +} diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index e96f721..6e38e7c 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,7 +1,6 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; @@ -20,6 +19,10 @@ public sealed class WeaponCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; + public PrimaryId CustomSetId { get; private set; } + public SecondaryId CustomWeaponId { get; private set; } + public Variant CustomVariant { get; private set; } + public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites) : base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log) { @@ -47,8 +50,9 @@ public sealed class WeaponCombo : FilterComboCache public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) { - _innerWidth = innerWidth; - _currentItem = previewIdx; + _innerWidth = innerWidth; + _currentItem = previewIdx; + CustomVariant = 0; return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); } @@ -75,6 +79,24 @@ public sealed class WeaponCombo : FilterComboCache return ret; } + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full tuple of set id, weapon id and variant, and set a custom item for that. + if (!ImGui.GetIO().KeyCtrl) + return; + + var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length != 3 + || !ushort.TryParse(split[0], out var setId) + || !ushort.TryParse(split[1], out var weaponId) + || !byte.TryParse(split[2], out var variant)) + return; + + CustomSetId = setId; + CustomWeaponId = weaponId; + CustomVariant = variant; + } + protected override bool IsVisible(int globalIndex, LowerString filter) => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); diff --git a/Penumbra.GameData b/Penumbra.GameData index 002260d..1019b56 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 002260d9815e571f1496c50374f5b712818e9880 +Subproject commit 1019b56de3b7ab2a6a1aefd699f9a507323e92fc From c1e1476fa647cdf50a36757bd63b4ca5becc29f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:30:47 +0200 Subject: [PATCH 121/212] Fix some issues with glamourer not searching mods by name. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 3f93551..d66ddc4 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -172,14 +172,14 @@ public class PenumbraService : IDisposable if (_queryTemporaryModSettings != null) { - var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source); + var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source, 0, modName); if (tempEc is PenumbraApiEc.Success && tempTuple != null) return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, tempTuple.Value.ForceInherit, false); } source = string.Empty; - var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory); + var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory, modName); if (ec2 is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -265,7 +265,7 @@ public class PenumbraService : IDisposable if (!Available) return; - if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName) == PenumbraApiEc.ModMissing) + if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); } @@ -349,14 +349,14 @@ public class PenumbraService : IDisposable var ex = settings.Remove ? index.HasValue - ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) - : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key) + ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key, mod.Name) + : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key, mod.Name) : index.HasValue ? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key) + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key, mod.Name) : _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key); + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key, mod.Name); switch (ex) { case PenumbraApiEc.InvalidArgument: @@ -384,8 +384,8 @@ public class PenumbraService : IDisposable private void SetModPermanent(StringBuilder sb, Mod mod, ModSettings settings, Guid collection) { var ec = settings.ForceInherit - ? _inheritMod!.Invoke(collection, mod.DirectoryName, true) - : _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); + ? _inheritMod!.Invoke(collection, mod.DirectoryName, true, mod.Name) + : _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled, mod.Name); switch (ec) { case PenumbraApiEc.ModMissing: @@ -399,14 +399,14 @@ public class PenumbraService : IDisposable if (settings.ForceInherit || !settings.Enabled) return; - ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority); + ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority, mod.Name); Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail."); foreach (var (setting, list) in settings.Settings) { ec = list.Count == 1 - ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0]) - : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); + ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0], mod.Name) + : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list, mod.Name); switch (ec) { case PenumbraApiEc.OptionGroupMissing: sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); break; From 8a9877bb014eb6493b6c3b21157e036cc1fa2c9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:31:26 +0200 Subject: [PATCH 122/212] Add testing Dynamis IPC for debugging. --- Glamourer/Glamourer.cs | 2 ++ Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 +++-- Glamourer/Services/ServiceManager.cs | 3 ++- OtterGui | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 1e2a62d..f62085a 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -26,6 +26,7 @@ public class Glamourer : IDalamudPlugin public static readonly Logger Log = new(); public static MessageService Messager { get; private set; } = null!; + public static DynamisIpc Dynamis { get; private set; } = null!; private readonly ServiceManager _services; @@ -35,6 +36,7 @@ public class Glamourer : IDalamudPlugin { _services = StaticServiceManager.CreateProvider(pluginInterface, Log, this); Messager = _services.GetService(); + Dynamis = _services.GetService(); _services.EnsureRequiredServices(); _services.GetService(); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index fc4799f..a0f2491 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -45,9 +45,10 @@ public unsafe class ModelEvaluationPanel( ImGuiUtil.DrawTableColumn("Address"); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(actor.ToString()); + + Glamourer.Dynamis.DrawPointer(actor); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(model.ToString()); + Glamourer.Dynamis.DrawPointer(model); ImGui.TableNextColumn(); if (actor.IsCharacter) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 78a0bf0..0754313 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -112,7 +112,8 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static ServiceManager AddDesigns(this ServiceManager services) => services.AddSingleton() diff --git a/OtterGui b/OtterGui index d1ba194..9235599 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d1ba1942efaae219b06ebc27d43de6d1889af97d +Subproject commit 9235599dd6efd17067a06ad98066a6c0d5b625e0 From 9abd7f27671b44a039ea34b9f007f28209ad0d9f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 8 May 2025 23:44:16 +0200 Subject: [PATCH 123/212] Make Dynamis IPC working. --- Glamourer/Gui/Equipment/ItemCopyService.cs | 9 ++------- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs | 16 ++++++++++++++++ Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 6 ++++-- OtterGui | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs index e72a54b..ea37963 100644 --- a/Glamourer/Gui/Equipment/ItemCopyService.cs +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -1,18 +1,13 @@ -using Dalamud.Game.ClientState.Keys; -using Dalamud.Plugin.Services; -using Glamourer.Services; +using Glamourer.Services; using ImGuiNET; -using OtterGui.OtterGuiInternal.Enums; using OtterGui.Services; -using OtterGuiInternal; -using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public class ItemCopyService(ItemManager items, IKeyState keyState, DictStain stainData) : IUiService +public class ItemCopyService(ItemManager items, DictStain stainData) : IUiService { public EquipItem? Item { get; private set; } public Stain? Stain { get; private set; } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 90282e8..3df425f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,5 +1,4 @@ using Glamourer.Gui.Tabs.DebugTab.IpcTester; -using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; @@ -36,6 +35,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs new file mode 100644 index 0000000..92cd777 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs @@ -0,0 +1,16 @@ +using OtterGui.Services; +using Penumbra.GameData.Gui.Debug; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DynamisPanel(DynamisIpc dynamis) : IGameDataDrawer +{ + public string Label + => "Dynamis Interop"; + + public void Draw() + => dynamis.DrawDebugInfo(); + + public bool Disabled + => false; +} diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 3714c82..012e5ba 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -61,7 +61,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); ImGuiUtil.DrawTableColumn(_penumbra.Available - ? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString() + ? _penumbra.CutsceneParent((ushort)_gameObjectIndex).ToString() : "Penumbra Unavailable"); ImGuiUtil.DrawTableColumn("Redraw Object"); @@ -76,7 +76,9 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem } ImGuiUtil.DrawTableColumn("Last Tooltip Date"); - ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? $"{_penumbraTooltip.LastTooltip.ToLongTimeString()} ({_penumbraTooltip.LastType} {_penumbraTooltip.LastId})" : "Never"); + ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue + ? $"{_penumbraTooltip.LastTooltip.ToLongTimeString()} ({_penumbraTooltip.LastType} {_penumbraTooltip.LastId})" + : "Never"); ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("Last Click Date"); diff --git a/OtterGui b/OtterGui index 9235599..ce8f88e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9235599dd6efd17067a06ad98066a6c0d5b625e0 +Subproject commit ce8f88ee892536016756769e86bdf48132351d80 From c93370ec920f03d3fb32154cfdcf730ba6129abd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 May 2025 00:06:19 +0200 Subject: [PATCH 124/212] Again. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index ce8f88e..77485cd 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit ce8f88ee892536016756769e86bdf48132351d80 +Subproject commit 77485cdd92ffcadb58e183ea9147d4ba37a2b93b From e4b32343aeb4d515ee35b2b492d6e442abf4dc57 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:08:17 +0200 Subject: [PATCH 125/212] Update libraries. --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index 77485cd..9aeda9a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 77485cdd92ffcadb58e183ea9147d4ba37a2b93b +Subproject commit 9aeda9a892d9b971e32b10db21a8daf9c0b9ee53 diff --git a/Penumbra.Api b/Penumbra.Api index f578091..574ef3a 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f578091fa579fb098c21036b492ff6e6088184c9 +Subproject commit 574ef3a8afd42b949e713e247a0b812886f088bb diff --git a/Penumbra.GameData b/Penumbra.GameData index 1019b56..bb3b462 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 1019b56de3b7ab2a6a1aefd699f9a507323e92fc +Subproject commit bb3b462bbc5bc2a598c1ad8c372b0cb255551fe1 From 081ac6bf8b3489aae69a3d80fafe0e0a9db63584 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:09:41 +0200 Subject: [PATCH 126/212] Split ResetAdvanced into two parts. --- Glamourer/Configuration.cs | 4 +- Glamourer/Gui/DesignQuickBar.cs | 61 +++++++++++++++---- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 +- Glamourer/Services/ConfigMigrationService.cs | 13 +++- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateManager.cs | 61 ++++++++++++++++--- 6 files changed, 120 insertions(+), 26 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 1d3689d..c9ff0e6 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -81,7 +81,7 @@ public class Configuration : IPluginConfiguration, ISavable public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public QdbButtons QdbButtons { get; set; } = - QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced; + QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvancedDyes; [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] @@ -158,7 +158,7 @@ public class Configuration : IPluginConfiguration, ISavable public static class Constants { - public const int CurrentVersion = 7; + public const int CurrentVersion = 8; public static readonly ISortMode[] ValidSortModes = [ diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 5112d97..b64a5f2 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -19,14 +19,15 @@ namespace Glamourer.Gui; [Flags] public enum QdbButtons { - ApplyDesign = 0x01, - RevertAll = 0x02, - RevertAutomation = 0x04, - RevertAdvanced = 0x08, - RevertEquip = 0x10, - RevertCustomize = 0x20, - ReapplyAutomation = 0x40, - ResetSettings = 0x80, + ApplyDesign = 0x01, + RevertAll = 0x02, + RevertAutomation = 0x04, + RevertAdvancedDyes = 0x08, + RevertEquip = 0x10, + RevertCustomize = 0x20, + ReapplyAutomation = 0x40, + ResetSettings = 0x80, + RevertAdvancedCustomization = 0x100, } public sealed class DesignQuickBar : Window, IDisposable @@ -124,6 +125,7 @@ public sealed class DesignQuickBar : Window, IDisposable DrawRevertEquipButton(buttonSize); DrawRevertCustomizeButton(buttonSize); DrawRevertAdvancedCustomization(buttonSize); + DrawRevertAdvancedDyes(buttonSize); DrawRevertAutomationButton(buttonSize); DrawReapplyAutomationButton(buttonSize); DrawResetSettingsButton(buttonSize); @@ -318,7 +320,7 @@ public sealed class DesignQuickBar : Window, IDisposable private void DrawRevertAdvancedCustomization(Vector2 buttonSize) { - if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) return; var available = 0; @@ -327,7 +329,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - _tooltipBuilder.Append("Left-Click: Revert the advanced customizations and dyes of the player character to their game state."); + _tooltipBuilder.Append("Left-Click: Revert the advanced customizations of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) @@ -335,7 +337,40 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) _tooltipBuilder.Append('\n'); available |= 2; - _tooltipBuilder.Append("Right-Click: Revert the advanced customizations and dyes of ") + _tooltipBuilder.Append("Right-Click: Revert the advanced customizations of ") + .Append(_targetIdentifier) + .Append(" to their game state."); + } + + if (available == 0) + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); + + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.PaintBrush, buttonSize, available); + ImGui.SameLine(); + if (clicked) + _stateManager.ResetAdvancedCustomizations(state!, StateSource.Manual); + } + + private void DrawRevertAdvancedDyes(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + return; + + var available = 0; + _tooltipBuilder.Clear(); + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + _tooltipBuilder.Append("Left-Click: Revert the advanced dyes of the player character to their game state."); + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + _tooltipBuilder.Append('\n'); + available |= 2; + _tooltipBuilder.Append("Right-Click: Revert the advanced dyes of ") .Append(_targetIdentifier) .Append(" to their game state."); } @@ -346,7 +381,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetAdvancedState(state!, StateSource.Manual); + _stateManager.ResetAdvancedDyes(state!, StateSource.Manual); } private void DrawRevertCustomizeButton(Vector2 buttonSize) @@ -501,7 +536,7 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; } - if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 746bb47..cf57824 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -251,7 +251,7 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 9 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); @@ -268,7 +268,8 @@ public class SettingsTab( ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), ("Revert Equip", true, QdbButtons.RevertEquip), ("Revert Customize", true, QdbButtons.RevertCustomize), - ("Revert Advanced", true, QdbButtons.RevertAdvanced), + ("Revert Advanced Customization", true, QdbButtons.RevertAdvancedCustomization), + ("Revert Advanced Dyes", true, QdbButtons.RevertAdvancedDyes), ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), ]; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 3f997c9..ef39f1a 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -24,9 +24,20 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator MigrateV4To5(); MigrateV5To6(); MigrateV6To7(); + MigrateV7To8(); AddColors(config, true); } + private void MigrateV7To8() + { + if (_config.Version > 7) + return; + + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + _config.QdbButtons |= QdbButtons.RevertAdvancedCustomization; + _config.Version = 8; + } + private void MigrateV6To7() { if (_config.Version > 6) @@ -43,7 +54,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator return; if (_data["ShowRevertAdvancedParametersButton"]?.ToObject() ?? true) - _config.QdbButtons |= QdbButtons.RevertAdvanced; + _config.QdbButtons |= QdbButtons.RevertAdvancedCustomization; _config.Version = 6; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 698151f..93a3450 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -411,6 +411,6 @@ public class StateApplier( return actors; } - private ActorData GetData(ActorState state) + public ActorData GetData(ActorState state) => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 2dda310..98b12aa 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -270,16 +270,63 @@ public sealed class StateManager( state.Materials.Clear(); - var actors = ActorData.Invalid; + var objects = ActorData.Invalid; if (source is not StateSource.Game) - actors = Applier.ApplyAll(state, redraw, true); + objects = Applier.ApplyAll(state, redraw, true); Glamourer.Log.Verbose( - $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); // only invoke if we define this reset call as the final call in our state update. - if(isFinal) - StateFinalized.Invoke(StateFinalizationType.Revert, actors); + if (isFinal) + StateFinalized.Invoke(StateFinalizationType.Revert, objects); + } + + public void ResetAdvancedDyes(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + state.ModelData.Parameters = state.BaseData.Parameters; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state.Sources[flag] = StateSource.Game; + + var objects = Applier.GetData(state); + if (source is not StateSource.Game) + foreach (var (idx, mat) in state.Materials.Values) + Applier.ChangeMaterialValue(state, objects, MaterialValueIndex.FromKey(idx), mat.Game); + + state.Materials.Clear(); + + Glamourer.Log.Verbose( + $"Reset advanced dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + } + + public void ResetAdvancedCustomizations(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + state.ModelData.Parameters = state.BaseData.Parameters; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state.Sources[flag] = StateSource.Game; + + var objects = ActorData.Invalid; + if (source is not StateSource.Game) + objects = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + + state.Materials.Clear(); + + Glamourer.Log.Verbose( + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -468,7 +515,7 @@ public sealed class StateManager( || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(isFinal) + if (isFinal) StateFinalized.Invoke(StateFinalizationType.Reapply, data); } From aa1ac291829f7f9948b8b10bcc8744f8fe4b7a04 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:16:55 +0200 Subject: [PATCH 127/212] Make design selector resizable. --- Glamourer/EphemeralConfig.cs | 4 ++++ .../DesignTab/DesignFileSystemSelector.cs | 24 +++++++++++++++++++ Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 5 +--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 3e13dc4..98dabec 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -20,6 +20,10 @@ public class EphemeralConfig : ISavable public Guid SelectedQuickDesign { get; set; } = Guid.Empty; public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; + public float CurrentDesignSelectorWidth { get; set; } = 200f; + public float DesignSelectorMinimumScale { get; set; } = 0.1f; + public float DesignSelectorMaximumScale { get; set; } = 0.5f; + [JsonIgnore] private readonly SaveService _saveService; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index ea117c5..11e803b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -12,6 +12,7 @@ using OtterGui.Filesystem; using OtterGui.FileSystem.Selector; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DesignTab; @@ -45,6 +46,29 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _config.Ephemeral.CurrentDesignSelectorWidth * ImUtf8.GlobalScale; + + protected override float MinimumAbsoluteRemainder + => 470 * ImUtf8.GlobalScale; + + protected override float MinimumScaling + => _config.Ephemeral.DesignSelectorMinimumScale; + + protected override float MaximumScaling + => _config.Ephemeral.DesignSelectorMaximumScale; + + protected override void SetSize(Vector2 size) + { + base.SetSize(size); + var adaptedSize = MathF.Round(size.X / ImUtf8.GlobalScale); + if (adaptedSize == _config.Ephemeral.CurrentDesignSelectorWidth) + return; + + _config.Ephemeral.CurrentDesignSelectorWidth = adaptedSize; + _config.Ephemeral.Save(); + } + public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event, Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, DesignColors designColors, DesignApplier designApplier) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 9832451..afb5900 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -16,7 +16,7 @@ public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, I public void DrawContent() { - _selector.Draw(GetDesignSelectorSize()); + _selector.Draw(); if (_importService.CreateCharaTarget(out var designBase, out var name)) { var newDesign = _manager.CreateClone(designBase, name, true); @@ -27,7 +27,4 @@ public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, I _panel.Draw(); _importService.CreateCharaSource(); } - - public float GetDesignSelectorSize() - => 200f * ImGuiHelpers.GlobalScale; } From f192c17c9bcb99b720b413b4aad16a095dafd8de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:46:56 +0200 Subject: [PATCH 128/212] Allow drag & drop for color buttons. --- .../CustomizationDrawer.Color.cs | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 4d34a05..4cc6ac3 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -4,13 +4,80 @@ using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; +using OtterGui.Text.EndObjects; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using System; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer { - private const string ColorPickerPopupName = "ColorPicker"; + private const string ColorPickerPopupName = "ColorPicker"; + private CustomizeValue _draggedColorValue; + private CustomizeIndex _draggedColorType; + + + private void DrawDragDropSource(CustomizeIndex index, CustomizeData custom) + { + using var dragDropSource = ImUtf8.DragDropSource(); + if (!dragDropSource) + return; + + if (!DragDropSource.SetPayload("##colorDragDrop"u8)) + _draggedColorValue = _customize[index]; + ImUtf8.Text( + $"Dragging {(custom.Color == 0 ? $"{_currentOption} (NPC)" : _currentOption)} #{_draggedColorValue.Value}..."); + _draggedColorType = index; + } + + private void DrawDragDropTarget(CustomizeIndex index) + { + using var dragDropTarget = ImUtf8.DragDropTarget(); + if (!dragDropTarget.Success || !dragDropTarget.IsDropping("##colorDragDrop"u8)) + return; + + var idx = _set.DataByValue(_draggedColorType, _draggedColorValue, out var draggedData, _customize.Face); + var bestMatch = _draggedColorValue; + if (draggedData.HasValue) + { + var draggedColor = draggedData.Value.Color; + var targetData = _set.Data(index, idx); + if (targetData.Color != draggedColor) + { + var bestDiff = Diff(targetData.Color, draggedColor); + var count = _set.Count(index); + for (var i = 0; i < count; ++i) + { + targetData = _set.Data(index, i); + if (targetData.Color == draggedColor) + { + UpdateValue(_draggedColorValue); + return; + } + + var diff = Diff(targetData.Color, draggedColor); + if (diff >= bestDiff) + continue; + + bestDiff = diff; + bestMatch = (CustomizeValue)i; + } + } + } + + UpdateValue(bestMatch); + return; + + static uint Diff(uint color1, uint color2) + { + var r = (color1 & 0xFF) - (color2 & 0xFF); + var g = ((color1 >> 8) & 0xFF) - ((color2 >> 8) & 0xFF); + var b = ((color1 >> 16) & 0xFF) - ((color2 >> 16) & 0xFF); + return 30 * r * r + 59 * g * g + 11 * b * b; + } + } private void DrawColorPicker(CustomizeIndex index) { @@ -21,7 +88,7 @@ public partial class CustomizationDrawer using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) { - if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) + if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.NoDragDrop, _framedIconSize)) { ImGui.OpenPopup(ColorPickerPopupName); } @@ -30,6 +97,9 @@ public partial class CustomizationDrawer var data = _set.Data(_currentIndex, current, _customize.Face); UpdateValue(data.Value); } + + DrawDragDropSource(index, custom); + DrawDragDropTarget(index); } var npc = false; From 74674cfa0c52d6e24dd63276a35e274c7ebdf29b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 23 May 2025 10:47:47 +0200 Subject: [PATCH 129/212] Make combos start from preview selection if possible. --- Glamourer/Gui/Customization/CustomizationDrawer.Color.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 8 -------- OtterGui | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 4cc6ac3..8db07ff 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -37,7 +37,7 @@ public partial class CustomizationDrawer using var dragDropTarget = ImUtf8.DragDropTarget(); if (!dragDropTarget.Success || !dragDropTarget.IsDropping("##colorDragDrop"u8)) return; - + var idx = _set.DataByValue(_draggedColorType, _draggedColorValue, out var draggedData, _customize.Face); var bestMatch = _draggedColorValue; if (draggedData.HasValue) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 9444ff7..0efa358 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,7 +1,6 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Widgets; @@ -13,13 +12,6 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom : _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), MouseWheelType.Control, Glamourer.Log) { - protected override void OnMouseWheel(string preview, ref int current, int steps) - { - if (CurrentSelectionIdx < 0) - CurrentSelectionIdx = Items.IndexOf(preview); - base.OnMouseWheel(preview, ref current, steps); - } - protected override bool DrawSelectable(int globalIdx, bool selected) { var isAutomatic = !_skipAutomatic && globalIdx == 0; diff --git a/OtterGui b/OtterGui index 9aeda9a..421874a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9aeda9a892d9b971e32b10db21a8daf9c0b9ee53 +Subproject commit 421874a12540b7f8c1279dcc6a92e895a94d2fbc From b4485f028d29e0e2a99e20a348f819066224cc83 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 23 May 2025 15:21:14 +0200 Subject: [PATCH 130/212] Batch some multi-design changes to skip unnecessary computations. --- Glamourer/Designs/History/EditorHistory.cs | 4 +- Glamourer/Gui/DesignCombo.cs | 50 ++++++++++++++++++- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 24 +++++++-- Glamourer/Services/ServiceManager.cs | 3 +- OtterGui | 2 +- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 58bce3d..caec151 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -152,7 +152,7 @@ public class EditorHistory : IDisposable, IService { if (!_stateEntries.TryGetValue(state, out var list)) { - list = new Queue(); + list = []; _stateEntries.Add(state, list); } @@ -163,7 +163,7 @@ public class EditorHistory : IDisposable, IService { if (!_designEntries.TryGetValue(design, out var list)) { - list = new Queue(); + list = []; _designEntries.Add(design, list); } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index a871cf1..c1e474d 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -10,6 +10,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; +using OtterGui.Services; using OtterGui.Widgets; namespace Glamourer.Gui; @@ -21,6 +22,7 @@ public abstract class DesignComboBase : FilterComboCache>> generator, Logger log, DesignChanged designChanged, @@ -32,6 +34,7 @@ public abstract class DesignComboBase : FilterComboCache Combos = services.GetServicesImplementing().ToArray(); + + internal DesignComboListener StopListening() + { + var list = new List(Combos.Count); + foreach (var combo in Combos.Where(c => c.IsListening)) + { + combo.StopListening(); + list.Add(combo); + } + + return new DesignComboListener(list); + } + + internal readonly struct DesignComboListener(List combos) : IDisposable + { + public void Dispose() + { + foreach (var combo in combos) + combo.StartListening(); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 3567fb6..ec8b465 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,7 +3,6 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; -using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; @@ -11,7 +10,12 @@ using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, Configuration config) +public class MultiDesignPanel( + DesignFileSystemSelector selector, + DesignManager editor, + DesignColors colors, + Configuration config, + DesignComboWrapper combos) { private readonly Button[] _leftButtons = []; private readonly Button[] _rightButtons = [new IncognitoButton(config)]; @@ -201,16 +205,23 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, true); + } ImGui.SameLine(); tt = _numQuickDesignEnabled == 0 ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, false); + } + ImGui.Separator(); } @@ -327,8 +338,11 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in _addDesigns) editor.ChangeColor(design, _colorCombo.CurrentSelection!); + } label = _removeDesigns.Count > 0 ? $"Unset {_removeDesigns.Count} Designs" @@ -338,8 +352,11 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) + { + using var disableListener = combos.StopListening(); foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); + } ImGui.Separator(); } @@ -455,7 +472,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) { - editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); + editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, + equip); if (equip.HasValue) { editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0754313..6c30d68 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -169,5 +169,6 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } diff --git a/OtterGui b/OtterGui index 421874a..cee50c3 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 421874a12540b7f8c1279dcc6a92e895a94d2fbc +Subproject commit cee50c3fe97a03ca7445c81de651b609620da526 From 5b59e74417e0fc3d90ea4b7a66be4972bc2cadd3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 27 May 2025 12:06:29 +0200 Subject: [PATCH 131/212] Use CS ReadStainingTemplate again. --- Glamourer/Interop/Material/PrepareColorSet.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index dfa6811..cf9be19 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,5 +1,4 @@ using Dalamud.Hooking; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; @@ -51,10 +50,6 @@ public sealed unsafe class PrepareColorSet private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2); - // TODO use CS when stabilized in Dalamud. - [Signature("E8 ?? ?? ?? ?? 48 8B FB EB 07")] - private static delegate* unmanaged _readStainingTemplate = null; - private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2) { Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}."); @@ -84,10 +79,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - _readStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); + MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - _readStainingTemplate(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); + MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); } table = newTable; From 07df3186c28e4e549075cb0ba3c73826a4e5e97b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 27 May 2025 14:38:04 +0200 Subject: [PATCH 132/212] Better. --- Glamourer/Interop/Material/PrepareColorSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index cf9be19..342bb74 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -79,10 +79,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); + material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); + material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); } table = newTable; From a0d2c39f45ba17e12c931c64c3d717018177e089 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 May 2025 13:56:06 +0200 Subject: [PATCH 133/212] 1.4.0.0 --- Glamourer.Api | 2 +- Glamourer/Gui/GlamourerChangelog.cs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index 9c86a9d..64897c0 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 9c86a9d6847f68e75679fe57d34f53f680d949c6 +Subproject commit 64897c069d3b6c3fe027b3ae0e832828728b9108 diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index e12b32e..a62743c 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -42,6 +42,7 @@ public class GlamourerChangelog Add1_3_6_0(Changelog); Add1_3_7_0(Changelog); Add1_3_8_0(Changelog); + Add1_4_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -62,6 +63,31 @@ public class GlamourerChangelog } } + private static void Add1_4_0_0(Changelog log) + => log.NextVersion("Version 1.4.0.0") + .RegisterHighlight("The design selector width is now draggable within certain restrictions that depend on the total window width.") + .RegisterEntry("The current behavior may not be final, let me know if you have any comments.", 1) + .RegisterEntry("Regular customization colors can now be dragged & dropped onto other customizations.") + .RegisterEntry( + "If no identical color is available in the target slot, the most similar color available (for certain values of similar) will be chosen instead.", + 1) + .RegisterEntry("Resetting advanced dyes and customizations has been split into two buttons for the quick design bar.") + .RegisterEntry("Weapons now also support custom ID input in the combo search box.") + .RegisterEntry("Added new IPC methods GetExtendedDesignData, AddDesign, DeleteDesign, GetDesignBase64, GetDesignJObject.") + .RegisterEntry("Added the option to prevent immediate repeats for random design selection (Thanks Diorik!).") + .RegisterEntry("Optimized some multi-design changes when selecting many designs and changing them at once.") + .RegisterEntry("Fixed item combos not starting from the currently selected item when scrolling them via mouse wheel.") + .RegisterEntry("Fixed some issue with Glamourer not searching mods by name for mod associations in some cases.") + .RegisterEntry("Fixed the IPC methods SetMetaState and SetMetaStateName not working (Thanks Caraxi!).") + .RegisterEntry("Added new IPC method GetDesignListExtended. (1.3.8.6)") + .RegisterEntry( + "Improved the naming of NPCs for identifiers by using Haselnussbombers new naming functionality (Thanks Hasel!). (1.3.8.6)") + .RegisterEntry( + "Added a modifier key separate from the delete modifier key that is used for less important key-checks, specifically toggling incognito mode. (1.3.8.5)") + .RegisterEntry("Used better Penumbra IPC for some things. (1.3.8.5)") + .RegisterEntry("Fixed an issue with advanced dyes for weapons. (1.3.8.5)") + .RegisterEntry("Fixed an issue with NPC automation due to missing job detection. (1.3.8.1)"); + private static void Add1_3_8_0(Changelog log) => log.NextVersion("Version 1.3.8.0") .RegisterImportant("Updated Glamourer for update 7.20 and Dalamud API 12.") From b8e1e7c3842ec602997b4ef2f724f030093d7858 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 28 May 2025 11:58:09 +0000 Subject: [PATCH 134/212] [CI] Updating repo.json for 1.4.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 3f1f29b..2ac55a2 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.6", - "TestingAssemblyVersion": "1.3.8.6", + "AssemblyVersion": "1.4.0.0", + "TestingAssemblyVersion": "1.4.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 56bbf6593af82af67008177bcac08ab96c8721b0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 May 2025 02:55:11 +0200 Subject: [PATCH 135/212] Fix button counting. --- Glamourer/Gui/DesignQuickBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b64a5f2..d83fce9 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -538,6 +538,8 @@ public sealed class DesignQuickBar : Window, IDisposable if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) From 66bed4217f01fb85bbee7024a50afaf0412f4d85 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 May 2025 02:55:20 +0200 Subject: [PATCH 136/212] Fix staining template reading. --- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 342bb74..f52bb68 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -82,7 +82,7 @@ public sealed unsafe class PrepareColorSet material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); + material->ReadStainingTemplate(dyeTable, stainIds.Stain2.Id, (Half*)&newTable, 1); } table = newTable; From d7b189b7148ec92052b16119afaadea93cc86acb Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 29 May 2025 00:57:24 +0000 Subject: [PATCH 137/212] [CI] Updating repo.json for 1.4.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 2ac55a2..47bc610 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.0", - "TestingAssemblyVersion": "1.4.0.0", + "AssemblyVersion": "1.4.0.1", + "TestingAssemblyVersion": "1.4.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 282935c6d677417ff371b075e9541bfb4b17b001 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 3 Jun 2025 18:40:41 +0200 Subject: [PATCH 138/212] Allow drag & drop of equipment pieces. --- Glamourer/Gui/Equipment/EquipItemSlotCache.cs | 83 +++++++++++++++++++ Glamourer/Gui/Equipment/EquipmentDrawer.cs | 56 ++++++++++++- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 1 + Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + Glamourer/Services/ItemManager.cs | 30 +++++++ Penumbra.Api | 2 +- 6 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 Glamourer/Gui/Equipment/EquipItemSlotCache.cs diff --git a/Glamourer/Gui/Equipment/EquipItemSlotCache.cs b/Glamourer/Gui/Equipment/EquipItemSlotCache.cs new file mode 100644 index 0000000..20aaf11 --- /dev/null +++ b/Glamourer/Gui/Equipment/EquipItemSlotCache.cs @@ -0,0 +1,83 @@ +using Glamourer.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +[InlineArray(13)] +public struct EquipItemSlotCache +{ + private EquipItem _element; + + public EquipItem Dragged + { + get => this[^1]; + set => this[^1] = value; + } + + public void Clear() + => ((Span)this).Clear(); + + public EquipItem this[EquipSlot slot] + { + get => this[(int)slot.ToIndex()]; + set => this[(int)slot.ToIndex()] = value; + } + + public void Update(ItemManager items, in EquipItem item, EquipSlot startSlot) + { + if (item.Id == Dragged.Id && item.Type == Dragged.Type) + return; + + switch (startSlot) + { + case EquipSlot.MainHand: + { + Clear(); + this[EquipSlot.MainHand] = item; + if (item.Type is FullEquipType.Sword) + this[EquipSlot.OffHand] = items.FindClosestShield(item.ItemId, out var shield) ? shield : default; + else + this[EquipSlot.OffHand] = items.ItemData.Secondary.GetValueOrDefault(item.ItemId); + break; + } + case EquipSlot.OffHand: + { + Clear(); + if (item.Type is FullEquipType.Shield) + this[EquipSlot.MainHand] = items.FindClosestSword(item.ItemId, out var sword) ? sword : default; + else + this[EquipSlot.MainHand] = items.ItemData.Primary.GetValueOrDefault(item.ItemId); + this[EquipSlot.OffHand] = item; + break; + } + default: + { + this[EquipSlot.MainHand] = default; + this[EquipSlot.OffHand] = default; + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + if (startSlot == slot) + { + this[slot] = item; + continue; + } + + var slotItem = items.Identify(slot, item.PrimaryId, item.Variant); + if (!slotItem.Valid || slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0) + { + slotItem = items.Identify(EquipSlot.OffHand, item.PrimaryId, item.SecondaryId, 1, item.Type); + if (slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0) + slotItem = default; + } + + this[slot] = slotItem; + } + + break; + } + } + + Dragged = item; + } +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index f2ecc08..43fda84 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -37,7 +37,9 @@ public class EquipmentDrawer private float _requiredComboWidthUnscaled; private float _requiredComboWidth; - private Stain? _draggedStain; + private Stain? _draggedStain; + private EquipItemSlotCache _draggedItem; + private EquipSlot _dragTarget; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) @@ -80,6 +82,7 @@ public class EquipmentDrawer _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; _advancedMaterialColor = ColorId.AdvancedDyeActive.Value(); + _dragTarget = EquipSlot.Unknown; } private bool VerifyRestrictedGear(EquipDrawData data) @@ -429,8 +432,8 @@ public class EquipmentDrawer using var dragSource = ImUtf8.DragDropSource(); if (dragSource.Success) { - if (DragDropSource.SetPayload("stainDragDrop"u8)) - _draggedStain = stain; + DragDropSource.SetPayload("stainDragDrop"u8); + _draggedStain = stain; ImUtf8.Text($"Dragging {stain.Name}..."); } } @@ -455,6 +458,7 @@ public class EquipmentDrawer using var disabled = ImRaii.Disabled(data.Locked); var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); + DrawGearDragDrop(data); if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) @@ -495,6 +499,50 @@ public class EquipmentDrawer data.SetItem(item); } + private void DrawGearDragDrop(in EquipDrawData data) + { + if (data.CurrentItem.Valid) + { + using var dragSource = ImUtf8.DragDropSource(); + if (dragSource.Success) + { + DragDropSource.SetPayload("equipDragDrop"u8); + _draggedItem.Update(_items, data.CurrentItem, data.Slot); + } + } + + using var dragTarget = ImUtf8.DragDropTarget(); + if (!dragTarget) + return; + + var item = _draggedItem[data.Slot]; + if (!item.Valid) + return; + + _dragTarget = data.Slot; + if (!dragTarget.IsDropping("equipDragDrop"u8)) + return; + + data.SetItem(item); + _draggedItem.Clear(); + } + + public unsafe void DrawDragDropTooltip() + { + var payload = ImGui.GetDragDropPayload().NativePtr; + if (payload is null) + return; + + if (!MemoryMarshal.CreateReadOnlySpanFromNullTerminated(payload->DataType).SequenceEqual("equipDragDrop"u8)) + return; + + using var tt = ImUtf8.Tooltip(); + if (_dragTarget is EquipSlot.Unknown) + ImUtf8.Text($"Dragging {_draggedItem.Dragged.Name}..."); + else + ImUtf8.Text($"Converting to {_draggedItem[_dragTarget].Name}..."); + } + private static bool ResetOrClear(bool locked, bool clicked, bool allowRevert, bool allowClear, in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable { @@ -546,6 +594,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); _itemCopy.HandleCopyPaste(mainhand); + DrawGearDragDrop(mainhand); if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, default, out var c)) @@ -589,6 +638,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); _itemCopy.HandleCopyPaste(offhand); + DrawGearDragDrop(offhand); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5419f23..98d9157 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -238,6 +238,7 @@ public class ActorPanel ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + _equipmentDrawer.DrawDragDropTooltip(); } private void DrawParameterHeader() diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 65014a4..d2b98b2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -130,6 +130,7 @@ public class DesignPanel ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + _equipmentDrawer.DrawDragDropTooltip(); } private void DrawEquipmentMetaToggles() diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 5d6f074..2cc6461 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -174,6 +174,36 @@ public class ItemManager return NothingItem(offhandType); } + public bool FindClosestShield(ItemId id, out EquipItem item) + { + var list = ItemData.ByType[FullEquipType.Shield]; + try + { + item = list.Where(i => i.ItemId.Id > id.Id && i.ItemId.Id - id.Id < 50).MinBy(i => i.ItemId.Id); + return true; + } + catch + { + item = default; + return false; + } + } + + public bool FindClosestSword(ItemId id, out EquipItem item) + { + var list = ItemData.ByType[FullEquipType.Sword]; + try + { + item = list.Where(i => i.ItemId.Id < id.Id && id.Id - i.ItemId.Id < 50).MaxBy(i => i.ItemId.Id); + return true; + } + catch + { + item = default; + return false; + } + } + public EquipItem Identify(EquipSlot slot, PrimaryId id, SecondaryId type, Variant variant, FullEquipType mainhandType = FullEquipType.Unknown) { diff --git a/Penumbra.Api b/Penumbra.Api index 574ef3a..ff7b3b4 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 574ef3a8afd42b949e713e247a0b812886f088bb +Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f From 75c76a92b9aa08d351f0979a2daa816c37fbe5d5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 13 Jun 2025 17:17:58 +0200 Subject: [PATCH 139/212] Optimize design combos and file system. --- Glamourer/Api/DesignsApi.cs | 6 +- Glamourer/Designs/DesignFileSystem.cs | 13 +- Glamourer/Designs/Special/RandomPredicate.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 153 +++++++----------- .../AutomationTab/RandomRestrictionDrawer.cs | 2 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 28 +++- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 7 +- Glamourer/Services/ServiceManager.cs | 3 +- OtterGui | 2 +- 9 files changed, 92 insertions(+), 124 deletions(-) diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index e21e6cb..9b48ade 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -20,12 +20,12 @@ public class DesignsApi( => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); public Dictionary GetDesignListExtended() - => designs.Designs.ToDictionary(d => d.Identifier, - d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + => fileSystem.ToDictionary(kvp => kvp.Key.Identifier, + kvp => (kvp.Key.Name.Text, kvp.Value.FullName(), color.GetColor(kvp.Key), kvp.Key.QuickDesign)); public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) => designs.Designs.ByIdentifier(designId) is { } d - ? (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) + ? (d.Name.Text, fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) : (string.Empty, string.Empty, 0, false); public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index e985e32..4a1cb3d 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -114,14 +114,14 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable return; case DesignChanged.Type.Deleted: - if (FindLeaf(design, out var leaf1)) + if (TryGetValue(design, out var leaf1)) Delete(leaf1); return; case DesignChanged.Type.ReloadedAll: Reload(); return; case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName: - if (!FindLeaf(design, out var leaf2)) + if (!TryGetValue(design, out var leaf2)) return; var old = oldName.FixName(); @@ -150,15 +150,6 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable ? (string.Empty, false) : (DesignToIdentifier(design), true); - // Search the entire filesystem for the leaf corresponding to a design. - public bool FindLeaf(Design design, [NotNullWhen(true)] out Leaf? leaf) - { - leaf = Root.GetAllDescendants(ISortMode.Lexicographical) - .OfType() - .FirstOrDefault(l => l.Value == design); - return leaf != null; - } - internal static void MigrateOldPaths(SaveService saveService, Dictionary oldPaths) { if (oldPaths.Count == 0) diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs index efb3233..ae05f8f 100644 --- a/Glamourer/Designs/Special/RandomPredicate.cs +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -22,7 +22,7 @@ public interface IDesignPredicate : designs; private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs) - => (d, d.Name.Lower, d.Identifier.ToString(), fs.FindLeaf(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); + => (d, d.Name.Lower, d.Identifier.ToString(), fs.TryGetValue(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); } public static class RandomPredicate diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index c1e474d..e5f3785 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -10,7 +10,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; -using OtterGui.Services; using OtterGui.Widgets; namespace Glamourer.Gui; @@ -22,8 +21,8 @@ public abstract class DesignComboBase : FilterComboCache>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) @@ -34,7 +33,6 @@ public abstract class DesignComboBase : FilterComboCache _currentDesign == p.Item1); - UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null); - return CurrentSelectionIdx; - } - protected bool Draw(IDesignStandIn? currentDesign, string? label, float width) { _currentDesign = currentDesign; - InnerWidth = 400 * ImGuiHelpers.GlobalScale; + UpdateCurrentSelection(); + InnerWidth = 400 * ImGuiHelpers.GlobalScale; var name = label ?? "Select Design Here..."; bool ret; using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(currentDesign as Design)) : null) @@ -150,37 +123,52 @@ public abstract class DesignComboBase : FilterComboCache ReferenceEquals(s.Item1, CurrentSelection?.Item1)); + if (CurrentSelectionIdx >= 0) + { + UpdateSelection(Items[CurrentSelectionIdx]); + } + else if (Items.Count > 0) + { + CurrentSelectionIdx = 0; + UpdateSelection(Items[0]); + } + else + { + UpdateSelection(null); + } + + if (!priorState) + Cleanup(); + _isCurrentSelectionDirty = false; + } + + protected override int UpdateCurrentSelected(int currentSelected) + { + CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1); + UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null); + return CurrentSelectionIdx; + } + private void OnDesignChanged(DesignChanged.Type type, Design? _1, ITransaction? _2 = null) { - switch (type) + _isCurrentSelectionDirty = type switch { - case DesignChanged.Type.Created: - case DesignChanged.Type.Renamed: - case DesignChanged.Type.ChangedColor: - case DesignChanged.Type.Deleted: - case DesignChanged.Type.QuickDesignBar: - var priorState = IsInitialized; - if (priorState) - Cleanup(); - CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1)); - if (CurrentSelectionIdx >= 0) - { - UpdateSelection(Items[CurrentSelectionIdx]); - } - else if (Items.Count > 0) - { - CurrentSelectionIdx = 0; - UpdateSelection(Items[0]); - } - else - { - UpdateSelection(null); - } - - if (!priorState) - Cleanup(); - break; - } + DesignChanged.Type.Created => true, + DesignChanged.Type.Renamed => true, + DesignChanged.Type.ChangedColor => true, + DesignChanged.Type.Deleted => true, + DesignChanged.Type.QuickDesignBar => true, + _ => _isCurrentSelectionDirty, + }; } private void QuickSelectedDesignTooltip(IDesignStandIn? design) @@ -248,8 +236,7 @@ public abstract class DesignCombo : DesignComboBase public sealed class QuickDesignCombo : DesignCombo { - public QuickDesignCombo(DesignManager designs, - DesignFileSystem fileSystem, + public QuickDesignCombo(DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, @@ -257,9 +244,9 @@ public sealed class QuickDesignCombo : DesignCombo DesignColors designColors) : base(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Where(d => d.QuickDesign) - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Where(kvp => kvp.Key.QuickDesign) + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]) { @@ -300,7 +287,6 @@ public sealed class QuickDesignCombo : DesignCombo } public sealed class LinkDesignCombo( - DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, @@ -309,8 +295,8 @@ public sealed class LinkDesignCombo( DesignColors designColors) : DesignCombo(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]); @@ -324,8 +310,8 @@ public sealed class RandomDesignCombo( DesignColors designColors) : DesignCombo(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]) { @@ -351,7 +337,6 @@ public sealed class RandomDesignCombo( } public sealed class SpecialDesignCombo( - DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, @@ -361,8 +346,8 @@ public sealed class SpecialDesignCombo( EphemeralConfig config, RandomDesignGenerator rng, QuickSelectedDesign quickSelectedDesign) - : DesignComboBase(() => designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + : DesignComboBase(() => fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2) .Prepend(new Tuple(new RandomDesign(rng), string.Empty)) .Prepend(new Tuple(quickSelectedDesign, string.Empty)) @@ -380,29 +365,3 @@ public sealed class SpecialDesignCombo( autoDesignManager.AddDesign(set, CurrentSelection!.Item1); } } - -public class DesignComboWrapper(ServiceManager services) -{ - public readonly IReadOnlyList Combos = services.GetServicesImplementing().ToArray(); - - internal DesignComboListener StopListening() - { - var list = new List(Combos.Count); - foreach (var combo in Combos.Where(c => c.IsListening)) - { - combo.StopListening(); - list.Add(combo); - } - - return new DesignComboListener(list); - } - - internal readonly struct DesignComboListener(List combos) : IDisposable - { - public void Dispose() - { - foreach (var combo in combos) - combo.StartListening(); - } - } -} diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index e7efc09..ae3be7e 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -278,7 +278,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private void LookupTooltip(IEnumerable designs) { using var _ = ImRaii.Tooltip(); - var tt = string.Join('\n', designs.Select(d => _designFileSystem.FindLeaf(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t)); + var tt = string.Join('\n', designs.Select(d => _designFileSystem.TryGetValue(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t)); ImGui.TextUnformatted(tt.Length == 0 ? "Matches no currently existing designs." : "Matches the following designs:"); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index ede3e9e..342bc41 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -3,7 +3,9 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; using OtterGui.Extensions; +using OtterGui.Filesystem; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; @@ -19,6 +21,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ public void Draw() { + DrawButtons(); foreach (var (design, idx) in _designManager.Designs.WithIndex()) { using var t = ImRaii.TreeNode($"{design.Name}##{idx}"); @@ -26,7 +29,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ continue; DrawDesign(design, _designFileSystem); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta, + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, + design.Application.Meta, design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(base64); @@ -35,6 +39,26 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ } } + private void DrawButtons() + { + if (ImUtf8.Button("Generate 500 Test Designs"u8)) + for (var i = 0; i < 500; ++i) + { + var design = _designManager.CreateEmpty($"Test Designs/Test Design {i}", true); + _designManager.AddTag(design, "_DebugTest"); + } + + ImUtf8.SameLineInner(); + if (ImUtf8.Button("Remove All Test Designs"u8)) + { + var designs = _designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray(); + foreach (var design in designs) + _designManager.Delete(design); + if (_designFileSystem.Find("Test Designs", out var path) && path is DesignFileSystem.Folder { TotalChildren: 0 }) + _designFileSystem.Delete(path); + } + } + public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem) { using var table = ImRaii.Table("##equip", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); @@ -53,7 +77,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGui.TableNextRow(); ImGuiUtil.DrawTableColumn("Design File System Path"); if (fileSystem != null) - ImGuiUtil.DrawTableColumn(fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known"); + ImGuiUtil.DrawTableColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known"); ImGui.TableNextRow(); ImGuiUtil.DrawTableColumn("Creation"); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index ec8b465..1ecda0b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -14,8 +14,7 @@ public class MultiDesignPanel( DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, - Configuration config, - DesignComboWrapper combos) + Configuration config) { private readonly Button[] _leftButtons = []; private readonly Button[] _rightButtons = [new IncognitoButton(config)]; @@ -206,7 +205,6 @@ public class MultiDesignPanel( : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, true); } @@ -217,7 +215,6 @@ public class MultiDesignPanel( : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, false); } @@ -339,7 +336,6 @@ public class MultiDesignPanel( ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in _addDesigns) editor.ChangeColor(design, _colorCombo.CurrentSelection!); } @@ -353,7 +349,6 @@ public class MultiDesignPanel( ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) { - using var disableListener = combos.StopListening(); foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 6c30d68..0754313 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -169,6 +169,5 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton(); + .AddSingleton(); } diff --git a/OtterGui b/OtterGui index cee50c3..78528f9 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit cee50c3fe97a03ca7445c81de651b609620da526 +Subproject commit 78528f93ac253db0061d9a8244cfa0cee5c2f873 From 2e9a7004c696940085bf65bac506348927d0391b Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 13 Jun 2025 15:21:04 +0000 Subject: [PATCH 140/212] [CI] Updating repo.json for 1.4.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 47bc610..8b0be29 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.1", - "TestingAssemblyVersion": "1.4.0.1", + "AssemblyVersion": "1.4.0.2", + "TestingAssemblyVersion": "1.4.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c0a278ca2cc0d53cca1349b289bc94112a8b782f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 15 Jun 2025 23:33:54 +0200 Subject: [PATCH 141/212] Make designs update on mousewheel. --- Glamourer/Gui/DesignCombo.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e5f3785..e35ad5e 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -123,6 +123,14 @@ public abstract class DesignComboBase : FilterComboCache Date: Mon, 14 Jul 2025 17:09:42 +0200 Subject: [PATCH 142/212] Fix issue with invalid bonus items. --- Glamourer/Services/ItemManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 2cc6461..a885b54 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -145,8 +145,10 @@ public class ItemManager // Only from early designs as migration. if (!id.IsBonusItem || id.Id == 0) { - IsBonusItemValid(slot, (BonusItemId)id.Id, out var item); - return item; + if (IsBonusItemValid(slot, (BonusItemId)id.Id, out var item)) + return item; + + return EquipItem.BonusItemNothing(slot); } if (!id.IsCustom) From 8a1f03c27254f7f9f01c7ae237b39bce79931d6e Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 14 Jul 2025 15:12:49 +0000 Subject: [PATCH 143/212] [CI] Updating repo.json for 1.4.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 8b0be29..0c6e22f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.2", - "TestingAssemblyVersion": "1.4.0.2", + "AssemblyVersion": "1.4.0.3", + "TestingAssemblyVersion": "1.4.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 40b4a8fd7a87bb7c80654f0ed75085da027d8fa1 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Mon, 28 Jul 2025 08:37:57 -0700 Subject: [PATCH 144/212] Ensure Revert via Command invokes a StateFinalization type for Reset --- Glamourer/Services/CommandService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 3d40fa9..1b88a00 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -407,7 +407,7 @@ public class CommandService : IDisposable, IApiService foreach (var identifier in identifiers) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ResetState(state, StateSource.Manual); + _stateManager.ResetState(state, StateSource.Manual, isFinal: true); } From 4ef4e65d46bb15581a25b4983988534e353ec660 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Mon, 28 Jul 2025 08:43:13 -0700 Subject: [PATCH 145/212] Ensure that reverts called by API invoke their StateFinalizationType upon completing a reset state. --- Glamourer/Api/StateApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 43dc453..68c593b 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -272,7 +272,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; case ApplyFlag.Customization: _stateManager.ResetCustomize(state, source, key); break; - case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key); break; + case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key, true); break; } ApiHelpers.Lock(state, key, flags); From d6df9885dcc15940e1263d50533b1afd3d6c05a8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Aug 2025 00:07:27 +0200 Subject: [PATCH 146/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index bb3b462..82b4467 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bb3b462bbc5bc2a598c1ad8c372b0cb255551fe1 +Subproject commit 82b446721a9b9c99d2470c54ad49fe19ff4987e3 From 2c3bed6ba5964456e51b75f82bfe6f552517e833 Mon Sep 17 00:00:00 2001 From: Karou Date: Thu, 7 Aug 2025 21:50:23 -0400 Subject: [PATCH 147/212] Api 13 grunt work --- Glamourer.Api | 2 +- Glamourer/DesignPanelFlag.cs | 2 +- Glamourer/Designs/ApplicationCollection.cs | 2 +- Glamourer/Designs/ApplicationRules.cs | 2 +- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Glamourer.csproj | 3 ++- Glamourer/Gui/Colors.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.Color.cs | 2 +- .../Gui/Customization/CustomizationDrawer.GenderRace.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs | 8 ++++---- Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.cs | 2 +- Glamourer/Gui/Customization/CustomizeParameterDrawer.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Equipment/BonusItemCombo.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- Glamourer/Gui/Equipment/GlamourerColorCombo.cs | 2 +- Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/ItemCopyService.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 2 +- Glamourer/Gui/GenericPopupWindow.cs | 2 +- Glamourer/Gui/MainWindow.cs | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs | 2 +- .../Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/HeaderDrawer.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs | 2 +- .../Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 2 +- Glamourer/Gui/UiHelpers.cs | 2 +- Glamourer/Interop/CrestService.cs | 4 ++-- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/Material/LiveColorTablePreviewer.cs | 5 +++-- Glamourer/Services/CommandService.cs | 2 +- Glamourer/Services/DesignResolver.cs | 2 +- Glamourer/State/FunModule.cs | 2 +- Glamourer/packages.lock.json | 8 ++++---- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 92 files changed, 102 insertions(+), 100 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 64897c0..6589ecd 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 64897c069d3b6c3fe027b3ae0e832828728b9108 +Subproject commit 6589ecdde5dac55730797fcc594be301e820bfcc diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/DesignPanelFlag.cs index db84173..f9465d9 100644 --- a/Glamourer/DesignPanelFlag.cs +++ b/Glamourer/DesignPanelFlag.cs @@ -1,5 +1,5 @@ using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Text; using OtterGui.Text.EndObjects; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 13813a3..8beeb78 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -1,6 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Enums; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 0df4feb..281a940 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -1,7 +1,7 @@ using Glamourer.Api.Enums; using Glamourer.GameData; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Enums; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 172e10f..a8f3178 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; using Glamourer.Gui; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9dabbb2..40b1218 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer @@ -29,6 +29,7 @@ + diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 98deace..b2713eb 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 8db07ff..4f463d6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 2f67012..26e9002 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index eabb6f0..8599f8c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Textures.TextureWraps; using Glamourer.GameData; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; @@ -35,7 +35,7 @@ public partial class CustomizationDrawer var hasIcon = icon.TryGetWrap(out var wrap, out _); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize)) { ImGui.OpenPopup(IconSelectorPopup); } @@ -89,7 +89,7 @@ public partial class CustomizationDrawer : ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); var hasIcon = icon.TryGetWrap(out var wrap, out var _); - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize)) { UpdateValue(custom.Value); ImGui.CloseCurrentPopup(); @@ -215,7 +215,7 @@ public partial class CustomizationDrawer hasIcon = icon.TryGetWrap(out wrap, out _); } - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 2c797b8..ec5523f 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGuiInternal; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 4ec6146..349891c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -4,7 +4,7 @@ using Dalamud.Plugin.Services; using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 9bfb2f8..18a9d1a 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -3,7 +3,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.PalettePlus; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e35ad5e..6dfffef 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -5,7 +5,7 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d83fce9..2dee0e4 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -8,7 +8,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index 892d9f1..aa43da7 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 43fda84..91187a8 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -5,7 +5,7 @@ using Glamourer.Events; using Glamourer.Gui.Materials; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 527dbb5..3149e67 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 14f2e8a..7c0c3bc 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Excel.Sheets; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs index ea37963..6912f1f 100644 --- a/Glamourer/Gui/Equipment/ItemCopyService.cs +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -1,5 +1,5 @@ using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 6e38e7c..3029db7 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,6 +1,6 @@ using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index 502af14..5061862 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Glamourer.Gui.Materials; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index f21a2b7..a39db2e 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -12,7 +12,7 @@ using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Custom; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index ec25378..8cf81ff 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -8,7 +8,7 @@ using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 0c4433d..ce50ff2 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Designs; using Glamourer.Interop.Material; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Services; using OtterGui.Text; diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 1723333..ed6018e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -3,7 +3,7 @@ using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Data; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 98d9157..dcd3a12 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -11,7 +11,7 @@ using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index e46d651..7d132a1 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 4e5e15c..9751a71 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.ActorTab; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 831ee7c..da3b636 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.AutomationTab; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 8a08437..ce843c4 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Custom; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index b197a1a..ba2e424 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Game.ClientState.Objects.Enums; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Gui; diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index ae3be7e..8eba59b 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -4,7 +4,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 1600837..4b05e35 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -6,7 +6,7 @@ using Glamourer.Designs.Special; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Log; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 96730e8..dca0ce5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index b4bdc2a..e0ec4bd 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -2,7 +2,7 @@ using Glamourer.GameData; using Glamourer.Designs; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs index 5a02621..2202ceb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -1,5 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index df39f45..aee59b6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -1,5 +1,5 @@ using Glamourer.Automation; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 565b6ba..6c0995c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index a53a677..4bf7d7b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -1,5 +1,5 @@ using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index 11f27fd..7c61392 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -1,5 +1,5 @@ using Glamourer.Interop; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using Penumbra.GameData.Files; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index cd89aec..b760221 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 2345abc..287d373 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 342bc41..38f0077 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Filesystem; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 26be8ce..cf45077 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index c517070..370c4e5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,5 +1,5 @@ using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index aafc21a..f480f6d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index f57a57e..ca9ff7b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -1,5 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Game; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 9e11b99..8cbf57a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs index 500fddd..dbcb30c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -1,6 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using static Penumbra.GameData.Files.ShpkFile; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 1a78b24..f4e6925 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index 1499fcb..ea95a9d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 638bffc..e97d337 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -5,7 +5,7 @@ using Glamourer.Api.Enums; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index afbb86b..f82bfb3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index a0f2491..d1b42e8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -2,7 +2,7 @@ using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 966bc6f..0d93bb8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -4,7 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index a4d1224..97847ae 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Text; using Penumbra.GameData.Actors; diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 012e5ba..9992bc3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.Api.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index 99c79ed..b22008d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 0efa358..e9fe775 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,5 +1,5 @@ using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index cbf7acd..042f2a2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 11e803b..7341d31 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -5,7 +5,7 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index b81ffdf..7e5807e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Links; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index d2b98b2..5bb3d77 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -12,7 +12,7 @@ using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index afb5900..1b92291 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index bab25f0..02f00db 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -5,7 +5,7 @@ using Dalamud.Utility; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 6ec9a1c..76579c2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1ecda0b..1cdb171 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index d6baeb9..cb169ba 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index 7941c13..1b70e27 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -2,7 +2,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index c6166a3..29fe7ef 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -6,7 +6,7 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index 847b65e..8497ab4 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,5 +1,5 @@ using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 4efa4c3..318e017 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.NpcTab; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index b4d3740..1dc9331 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Filesystem; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs index 98ba870..7080b4d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Log; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 87490db..76435ec 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Interop.Penumbra; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index cf57824..311cf85 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -8,7 +8,7 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 903257b..947f0c6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -5,7 +5,7 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 6d9ce51..d75f2dc 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -6,7 +6,7 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Table; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index c2e06e5..661b2a4 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui; using OtterGui.Widgets; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 1ec79d7..94ddb06 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -2,7 +2,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Misc; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 95b3587..74b7800 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -122,7 +122,7 @@ public sealed unsafe class CrestService : EventWrapperRef3IsFreeCompanyCrestVisibleOnSlot(index) != 0; + return model.AsHuman->IsFreeCompanyCrestVisibleOnSlot(index); } case CrestType.Offhand: { @@ -130,7 +130,7 @@ public sealed unsafe class CrestService : EventWrapperRef3IsFreeCompanyCrestVisibleOnSlot(index) != 0; + return model.AsWeapon->IsFreeCompanyCrestVisibleOnSlot(index); } } diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index b9dfbe1..c6e90fd 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 94cb9ca..4190746 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; @@ -114,7 +114,8 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var frame = DateTimeOffset.UtcNow.UtcTicks; var hueByte = frame % (steps * frameLength) / frameLength; var hue = (float)hueByte / steps; - ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b); + float r, g, b = 0; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &r, &g, &b); return new Vector3(r, g, b); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 1b88a00..d47f26f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -9,7 +9,7 @@ using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs index 68b54bb..8bb5cd2 100644 --- a/Glamourer/Services/DesignResolver.cs +++ b/Glamourer/Services/DesignResolver.cs @@ -4,7 +4,7 @@ using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using OtterGui.Classes; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index ae7160c..6abb03a 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -4,7 +4,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index e6f2fe5..f66e6e4 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[12.0.0, )", - "resolved": "12.0.0", - "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" + "requested": "[13.0.0, )", + "resolved": "13.0.0", + "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", @@ -96,7 +96,7 @@ "type": "Project", "dependencies": { "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.6.1, )", + "Penumbra.Api": "[5.10.0, )", "Penumbra.String": "[1.0.6, )" } }, diff --git a/OtterGui b/OtterGui index 78528f9..9523b7a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 78528f93ac253db0061d9a8244cfa0cee5c2f873 +Subproject commit 9523b7ac725656b21fa98faef96962652e86e64f diff --git a/Penumbra.Api b/Penumbra.Api index ff7b3b4..c27a060 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f +Subproject commit c27a06004138f2ec80ccdb494bb6ddf6d39d2165 diff --git a/Penumbra.GameData b/Penumbra.GameData index 82b4467..65c5bf3 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 82b446721a9b9c99d2470c54ad49fe19ff4987e3 +Subproject commit 65c5bf3f46569a54b0057c9015ab839b4e2a4350 diff --git a/Penumbra.String b/Penumbra.String index 2896c05..878acce 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 2896c0561f60827f97408650d52a15c38f4d9d10 +Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd From 72e05e23bc77c877f29c039e6901117e3a973ed5 Mon Sep 17 00:00:00 2001 From: Karou Date: Thu, 7 Aug 2025 21:53:01 -0400 Subject: [PATCH 148/212] stupid detached head.... --- Glamourer.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index 6589ecd..1c51730 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 6589ecdde5dac55730797fcc594be301e820bfcc +Subproject commit 1c517301c9fd0818e2c02e72304d6de121b9d703 From c25f0f72db033fcc264ef3ee2c9f24c735bd288f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:36:14 +0200 Subject: [PATCH 149/212] Update for ottergui. --- Glamourer/Designs/DesignFileSystem.cs | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 4a1cb3d..fd47793 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -41,11 +41,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct CreationDate : ISortMode { - public string Name - => "Creation Date (Older First)"; + public ReadOnlySpan Name + => "Creation Date (Older First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate)); @@ -53,11 +53,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct UpdateDate : ISortMode { - public string Name - => "Update Date (Older First)"; + public ReadOnlySpan Name + => "Update Date (Older First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit)); @@ -65,11 +65,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct InverseCreationDate : ISortMode { - public string Name - => "Creation Date (Newer First)"; + public ReadOnlySpan Name + => "Creation Date (Newer First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate)); @@ -77,11 +77,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct InverseUpdateDate : ISortMode { - public string Name - => "Update Date (Newer First)"; + public ReadOnlySpan Name + => "Update Date (Newer First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit)); From 0f98fac157bdf693d186984e915cca9c36a98356 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:36:47 +0200 Subject: [PATCH 150/212] Add auto-locking to design defaults. --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignManager.cs | 3 +++ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index c9ff0e6..f128bdd 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -32,6 +32,7 @@ public class DefaultDesignSettings public bool ResetAdvancedDyes = false; public bool ShowQuickDesignBar = true; public bool ResetTemporarySettings = false; + public bool Locked = false; } public class Configuration : IPluginConfiguration, ISavable diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index fff9565..3ddfefd 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -110,6 +110,7 @@ public sealed class DesignManager : DesignEditor QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); SaveService.ImmediateSave(design); @@ -134,6 +135,7 @@ public sealed class DesignManager : DesignEditor ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); SaveService.ImmediateSave(design); @@ -153,6 +155,7 @@ public sealed class DesignManager : DesignEditor Name = actualName, Index = Designs.Count, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 311cf85..1ccb079 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -113,6 +113,8 @@ public class SettingsTab( if (!ImUtf8.CollapsingHeader("Design Defaults")) return; + Checkbox("Locked Designs"u8, "Newly created designs will be locked to prevent unintended changes."u8, + config.DefaultDesignSettings.Locked, v => config.DefaultDesignSettings.Locked = v); Checkbox("Show in Quick Design Bar"u8, "Newly created designs will be shown in the quick design bar by default."u8, config.DefaultDesignSettings.ShowQuickDesignBar, v => config.DefaultDesignSettings.ShowQuickDesignBar = v); Checkbox("Reset Advanced Dyes"u8, "Newly created designs will be configured to reset advanced dyes on application by default."u8, From 00d550f4feb344342bb5f3962d205c23feedc821 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:38:51 +0200 Subject: [PATCH 151/212] Add viera ear flags --- Glamourer.Api | 2 +- Glamourer/Automation/ApplicationType.cs | 2 +- Glamourer/Designs/ApplicationCollection.cs | 6 +- Glamourer/Designs/DesignBase.cs | 14 +++- Glamourer/Designs/DesignData.cs | 15 ++++ Glamourer/Designs/MetaIndex.cs | 13 ++- Glamourer/Events/PenumbraReloaded.cs | 3 + Glamourer/Events/VieraEarStateChanged.cs | 22 +++++ Glamourer/Events/VisorStateChanged.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 6 ++ .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 3 + .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 1 + .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 22 +++++ Glamourer/Interop/VieraEarService.cs | 82 +++++++++++++++++++ Glamourer/Interop/VisorService.cs | 6 +- Glamourer/Services/ServiceManager.cs | 2 + Glamourer/State/StateApplier.cs | 9 ++ Glamourer/State/StateIndex.cs | 4 +- Glamourer/State/StateListener.cs | 47 ++++++++++- Glamourer/State/StateManager.cs | 3 +- Penumbra.GameData | 2 +- 21 files changed, 245 insertions(+), 21 deletions(-) create mode 100644 Glamourer/Events/VieraEarStateChanged.cs create mode 100644 Glamourer/Interop/VieraEarService.cs diff --git a/Glamourer.Api b/Glamourer.Api index 1c51730..54c1944 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 1c517301c9fd0818e2c02e72304d6de121b9d703 +Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 58f296e..f72c93f 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -38,7 +38,7 @@ public static class ApplicationTypeExtensions var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; - var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) + var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.EarState : 0) | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 8beeb78..c03d4b4 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -19,13 +19,13 @@ public record struct ApplicationCollection( public static readonly ApplicationCollection None = new(0, 0, CustomizeFlag.BodyType, 0, 0, 0); public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All, - CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState | MetaFlag.EarState); public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All, MetaFlag.Wetness); public static readonly ApplicationCollection Default = new(EquipFlagExtensions.All, BonusExtensions.All, - CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState); public static ApplicationCollection FromKeys() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch @@ -47,7 +47,7 @@ public record struct ApplicationCollection( Equip = 0; BonusItem = 0; Crest = 0; - Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState); } public void RemoveCustomize() diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index b21c433..f87d75a 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -40,7 +40,8 @@ public class DesignBase } /// Used when importing .cma or .chara files. - internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, BonusItemFlag bonusFlags) + internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, + BonusItemFlag bonusFlags) { _designData = designData; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; @@ -254,9 +255,10 @@ public class DesignBase ret[slot.ToString()] = Serialize(item.Id, stains, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } - ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); - ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply"); - ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply"); + ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); + ret["VieraEars"] = new QuadBool(_designData.AreEarsVisible(), DoApplyMeta(MetaIndex.EarState)).ToJObject("Show", "Apply"); + ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply"); + ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply"); } else { @@ -603,6 +605,10 @@ public class DesignBase metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled); design._designData.SetVisor(metaValue.ForcedValue); + + metaValue = QuadBool.FromJObject(equip["VieraEars"], "Show", "Apply", QuadBool.NullTrue); + design.SetApplyMeta(MetaIndex.EarState, metaValue.Enabled); + design._designData.SetEarsVisible(metaValue.ForcedValue); return; void PrintWarning(string msg) diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index bba0ccb..c7ca8e5 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -287,6 +287,7 @@ public unsafe struct DesignData MetaIndex.HatState => IsHatVisible(), MetaIndex.VisorState => IsVisorToggled(), MetaIndex.WeaponState => IsWeaponVisible(), + MetaIndex.EarState => AreEarsVisible(), _ => false, }; @@ -297,6 +298,7 @@ public unsafe struct DesignData MetaIndex.HatState => SetHatVisible(value), MetaIndex.VisorState => SetVisor(value), MetaIndex.WeaponState => SetWeaponVisible(value), + MetaIndex.EarState => SetEarsVisible(value), _ => false, }; @@ -340,6 +342,9 @@ public unsafe struct DesignData public readonly bool IsWeaponVisible() => (_states & 0x08) == 0x08; + public readonly bool AreEarsVisible() + => (_states & 0x10) == 0x00; + public bool SetWeaponVisible(bool value) { if (value == IsWeaponVisible()) @@ -349,6 +354,15 @@ public unsafe struct DesignData return true; } + public bool SetEarsVisible(bool value) + { + if (value == AreEarsVisible()) + return false; + + _states = (byte)(value ? _states & ~0x10 : _states | 0x10); + return true; + } + public void SetDefaultEquipment(ItemManager items) { foreach (var slot in EquipSlotExtensions.EqdpSlots) @@ -386,6 +400,7 @@ public unsafe struct DesignData SetHatVisible(true); SetWeaponVisible(true); + SetEarsVisible(true); SetVisor(false); fixed (uint* ptr = _itemIds) { diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 3fc4655..1842ae3 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -10,14 +10,15 @@ public enum MetaIndex VisorState = StateIndex.MetaVisorState, WeaponState = StateIndex.MetaWeaponState, ModelId = StateIndex.MetaModelId, + EarState = StateIndex.MetaEarState, } public static class MetaExtensions { public static readonly IReadOnlyList AllRelevant = - [MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState]; + [MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState, MetaIndex.EarState]; - public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; + public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState; public static MetaFlag ToFlag(this MetaIndex index) => index switch @@ -26,6 +27,7 @@ public static class MetaExtensions MetaIndex.HatState => MetaFlag.HatState, MetaIndex.VisorState => MetaFlag.VisorState, MetaIndex.WeaponState => MetaFlag.WeaponState, + MetaIndex.EarState => MetaFlag.EarState, _ => (MetaFlag)byte.MaxValue, }; @@ -36,7 +38,8 @@ public static class MetaExtensions MetaFlag.HatState => MetaIndex.HatState, MetaFlag.VisorState => MetaIndex.VisorState, MetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, + MetaFlag.EarState => MetaIndex.EarState, + _ => (MetaIndex)byte.MaxValue, }; public static IEnumerable ToIndices(this MetaFlag index) @@ -49,6 +52,8 @@ public static class MetaExtensions yield return MetaIndex.VisorState; if (index.HasFlag(MetaFlag.WeaponState)) yield return MetaIndex.WeaponState; + if (index.HasFlag(MetaFlag.EarState)) + yield return MetaIndex.EarState; } public static string ToName(this MetaIndex index) @@ -58,6 +63,7 @@ public static class MetaExtensions MetaIndex.VisorState => "Visor Toggled", MetaIndex.WeaponState => "Weapon Visible", MetaIndex.Wetness => "Force Wetness", + MetaIndex.EarState => "Ears Visible", _ => "Unknown Meta", }; @@ -68,6 +74,7 @@ public static class MetaExtensions MetaIndex.VisorState => "Toggle the visor state of the characters head gear.", MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.", MetaIndex.Wetness => "Force the character to be wet or not.", + MetaIndex.EarState => "Hide or show the characters ears through the head gear. (Viera only)", _ => string.Empty, }; } diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 20b58f9..0975670 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -15,5 +15,8 @@ public sealed class PenumbraReloaded() /// VisorService = 0, + + /// + VieraEarService = 0, } } diff --git a/Glamourer/Events/VieraEarStateChanged.cs b/Glamourer/Events/VieraEarStateChanged.cs new file mode 100644 index 0000000..65730b8 --- /dev/null +++ b/Glamourer/Events/VieraEarStateChanged.cs @@ -0,0 +1,22 @@ +using OtterGui.Classes; +using Penumbra.GameData.Interop; + +namespace Glamourer.Events; + +/// +/// Triggered when the state of viera ear visibility for any draw object is changed. +/// +/// Parameter is the model with a changed viera ear visibility state. +/// Parameter is the new state. +/// Parameter is whether to call the original function. +/// +/// +public sealed class VieraEarStateChanged() + : EventWrapperRef2(nameof(VieraEarStateChanged)) +{ + public enum Priority + { + /// + StateListener = 0, + } +} diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index d2d3a6c..03b7336 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -19,4 +19,4 @@ public sealed class VisorStateChanged() /// StateListener = 0, } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index dcd3a12..224154b 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -305,6 +305,12 @@ public class ActorPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } + + ImGui.SameLine(); + using (_ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.EarState, _stateManager, _state!)); + } } private void DrawMonsterPanel() diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index e0ec4bd..35642a7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -87,6 +87,9 @@ public class ActiveStatePanel(StateManager _stateManager, ActorObjectManager _ob PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), state.Sources[MetaIndex.VisorState]); ImGui.TableNextRow(); + PrintRow("Viera Ears Visible", state.BaseData.AreEarsVisible(), state.ModelData.AreEarsVisible(), + state.Sources[MetaIndex.EarState]); + ImGui.TableNextRow(); PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), state.Sources[MetaIndex.WeaponState]); ImGui.TableNextRow(); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 38f0077..7c60dda 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -114,6 +114,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGuiUtil.DrawTableColumn(index.ToName()); ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString()); ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep"); + ImGui.TableNextRow(); } ImGuiUtil.DrawTableColumn("Model ID"); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index d1b42e8..185e19b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -17,6 +17,7 @@ namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class ModelEvaluationPanel( ActorObjectManager _objectManager, VisorService _visorService, + VieraEarService _vieraEarService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, CrestService _crestService, @@ -84,6 +85,7 @@ public unsafe class ModelEvaluationPanel( ImGuiUtil.CopyOnClickSelectable(offhand.ToString()); DrawVisor(actor, model); + DrawVieraEars(actor, model); DrawHatState(actor, model); DrawWeaponState(actor, model); DrawWetness(actor, model); @@ -135,6 +137,26 @@ public unsafe class ModelEvaluationPanel( _visorService.SetVisorState(model, !VisorService.GetVisorState(model)); } + private void DrawVieraEars(Actor actor, Model model) + { + using var id = ImRaii.PushId("Viera Ears"); + ImGuiUtil.DrawTableColumn("Viera Ears"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.ShowVieraEars.ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.VieraEarsVisible.ToString() : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman) + return; + + if (ImGui.SmallButton("Set True")) + _vieraEarService.SetVieraEarState(model, true); + ImGui.SameLine(); + if (ImGui.SmallButton("Set False")) + _vieraEarService.SetVieraEarState(model, false); + ImGui.SameLine(); + if (ImGui.SmallButton("Toggle")) + _vieraEarService.SetVieraEarState(model, !model.VieraEarsVisible); + } + private void DrawHatState(Actor actor, Model model) { using var id = ImRaii.PushId("HatState"); diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs new file mode 100644 index 0000000..1e5c4eb --- /dev/null +++ b/Glamourer/Interop/VieraEarService.cs @@ -0,0 +1,82 @@ +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Glamourer.Events; +using Penumbra.GameData.Interop; + +namespace Glamourer.Interop; + +public unsafe class VieraEarService : IDisposable +{ + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VieraEarStateChanged Event; + + public VieraEarService(VieraEarStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) + { + _interop = interop; + _penumbra = penumbra; + Event = visorStateChanged; + _setupVieraEarHook = Create(); + _penumbra.Subscribe(Restore, PenumbraReloaded.Priority.VieraEarService); + } + + public void Dispose() + { + _setupVieraEarHook.Dispose(); + _penumbra.Unsubscribe(Restore); + } + + /// Obtain the current state of viera ears for the given draw object (true: toggled). + public static unsafe bool GetVieraEarState(Model characterBase) + => characterBase is { IsCharacterBase: true, VieraEarsVisible: true }; + + /// Manually set the state of the Visor for the given draw object. + /// The draw object. + /// The desired state (true: toggled). + /// Whether the state was changed. + public bool SetVieraEarState(Model human, bool on) + { + if (!human.IsHuman) + return false; + + var oldState = GetVieraEarState(human); + Glamourer.Log.Verbose($"[SetVieraEarState] Invoked manually on 0x{human.Address:X} switching from {oldState} to {on}."); + if (oldState == on) + return false; + + human.VieraEarsVisible = on; + return true; + } + + private delegate void UpdateVieraEarDelegateInternal(DrawDataContainer* drawData, byte on); + + private Hook _setupVieraEarHook; + + private void SetupVieraEarDetour(DrawDataContainer* drawData, byte value) + { + Actor actor = drawData->OwnerObject; + var originalOn = value != 0; + var on = originalOn; + // Invoke an event that can change the requested value + Event.Invoke(actor, ref on); + + Glamourer.Log.Verbose( + $"[SetVieraEarState] Invoked from game on 0x{actor.Address:X} switching to {on} (original {originalOn} from {value})."); + + _setupVieraEarHook.Original(drawData, on ? (byte)1 : (byte)0); + } + + private unsafe Hook Create() + { + var hook = _interop.HookFromSignature("E8 ?? ?? ?? ?? 48 8D 8F ?? ?? ?? ?? 4C 8D 4C 24", SetupVieraEarDetour); + hook.Enable(); + return hook; + } + + private void Restore() + { + _setupVieraEarHook.Dispose(); + _setupVieraEarHook = Create(); + } +} diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 9763682..25823b6 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -9,9 +9,9 @@ namespace Glamourer.Interop; public class VisorService : IDisposable { - private readonly PenumbraReloaded _penumbra; - private readonly IGameInteropProvider _interop; - public readonly VisorStateChanged Event; + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VisorStateChanged Event; public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0754313..6cfb4b6 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -71,6 +71,7 @@ public static class StaticServiceManager private static ServiceManager AddEvents(this ServiceManager services) => services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -96,6 +97,7 @@ public static class StaticServiceManager private static ServiceManager AddInterop(this ServiceManager services) => services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 93a3450..c346ab1 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -262,6 +262,14 @@ public class StateApplier( _visor.SetVisorState(actor.Model, value); return; } + case MetaIndex.EarState: + foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) + { + var model = actor.Model; + model.VieraEarsVisible = value; + } + + return; } } @@ -402,6 +410,7 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); + ChangeMetaState(actors, MetaIndex.EarState, state.ModelData.AreEarsVisible()); ChangeCrests(actors, state.ModelData.CrestVisibility); ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); ChangeMaterialValues(actors, state.Materials); diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index e3f1863..dff05a3 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -189,8 +189,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators MetaFlag.HatState, MetaVisorState => MetaFlag.VisorState, MetaWeaponState => MetaFlag.WeaponState, + MetaEarState => MetaFlag.EarState, MetaModelId => true, CrestHead => CrestFlag.Head, diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 90520b2..4b70718 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -9,6 +9,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.GameData; using Penumbra.GameData.DataContainers; @@ -39,6 +40,7 @@ public class StateListener : IDisposable private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; + private readonly VieraEarStateChanged _vieraEarState; private readonly WeaponVisibilityChanged _weaponVisibility; private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; @@ -62,7 +64,7 @@ public class StateListener : IDisposable WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized, VieraEarStateChanged vieraEarState) { _manager = manager; _items = items; @@ -88,6 +90,7 @@ public class StateListener : IDisposable _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; _stateFinalized = stateFinalized; + _vieraEarState = vieraEarState; Subscribe(); } @@ -266,7 +269,7 @@ public class StateListener : IDisposable private void OnGearsetDataLoaded(Actor actor, Model model) { - if (!actor.Valid || (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)) + if (!actor.Valid || _condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; // ensure actor and state are valid. @@ -710,6 +713,44 @@ public class StateListener : IDisposable } } + /// Handle visor state changes made by the game. + private void OnVieraEarChange(Actor actor, ref bool value) + { + // Value is inverted compared to our own handling. + + // Skip updates when in customize update. + if (ChangeCustomizeService.InUpdate.InMethod) + return; + + if (!actor.IsCharacter) + return; + + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors, out var identifier)) + return; + + if (!_manager.TryGetValue(identifier, out var state)) + return; + + // Update visor base state. + if (state.BaseData.SetEarsVisible(!value)) + { + // if base state changed, either overwrite the actual value if we have fixed values, + // or overwrite the stored model state with the new one. + if (state.Sources[MetaIndex.EarState].IsFixed()) + value = !state.ModelData.AreEarsVisible(); + else + _manager.ChangeMetaState(state, MetaIndex.EarState, !value, ApplySettings.Game); + } + else + { + // if base state did not change, overwrite the value with the model state one. + value = !state.ModelData.AreEarsVisible(); + } + } + /// Handle Hat Visibility changes. These act on the game object. private void OnHeadGearVisibilityChange(Actor actor, ref bool value) { @@ -802,6 +843,7 @@ public class StateListener : IDisposable _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); + _vieraEarState.Subscribe(OnVieraEarChange, VieraEarStateChanged.Priority.StateListener); _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); @@ -820,6 +862,7 @@ public class StateListener : IDisposable _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); + _vieraEarState.Unsubscribe(OnVieraEarChange); _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 98b12aa..e8926d6 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -158,6 +158,7 @@ public sealed class StateManager( // Visor state is a flag on the game object, but we can see the actual state on the draw object. ret.SetVisor(VisorService.GetVisorState(model)); + ret.SetEarsVisible(model.VieraEarsVisible); foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); @@ -186,7 +187,7 @@ public sealed class StateManager( off = actor.GetOffhand(); FistWeaponHack(ref ret, ref main, ref off); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); - + ret.SetEarsVisible(actor.ShowVieraEars); foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, actor.GetCrest(slot)); diff --git a/Penumbra.GameData b/Penumbra.GameData index 65c5bf3..17f2f49 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 65c5bf3f46569a54b0057c9015ab839b4e2a4350 +Subproject commit 17f2f496664b0d69ebd7fcdabe7bc8e3e20b6463 From ac6a726f57a2eefda348425ed6da5aec25bc58cd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:39:05 +0200 Subject: [PATCH 152/212] Remaining API13 updates. --- Glamourer/Glamourer.csproj | 1 - Glamourer/Glamourer.json | 2 +- .../Customization/CustomizeParameterDrawer.cs | 4 ++-- Glamourer/Gui/DesignCombo.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 ++-- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 4 ++-- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 5 +++++ .../SettingsTab/CollectionOverrideDrawer.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 8 ++++---- Glamourer/Interop/CrestService.cs | 2 +- .../Material/LiveColorTablePreviewer.cs | 6 +++--- Glamourer/Services/TextureService.cs | 19 ++++++++++--------- 16 files changed, 36 insertions(+), 31 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 40b1218..86ae713 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -29,7 +29,6 @@ - diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 3127d7d..2daff91 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 12, + "DalamudApiLevel": 13, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 18a9d1a..4db6b14 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -287,13 +287,13 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import } private ImGuiColorEditFlags GetFlags() - => Format | Display | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions; + => Format | Display | ImGuiColorEditFlags.Hdr | ImGuiColorEditFlags.NoOptions; private ImGuiColorEditFlags Format => config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8; private ImGuiColorEditFlags Display - => config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRGB : ImGuiColorEditFlags.DisplayHSV; + => config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRgb : ImGuiColorEditFlags.DisplayHsv; private ImRaii.IEndObject EnsureSize() { diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 6dfffef..2d8880e 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -194,7 +194,7 @@ public abstract class DesignComboBase : FilterComboCacheDataType).SequenceEqual("equipDragDrop"u8)) + if (!MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)Unsafe.AsPointer(ref payload->DataType_0)).SequenceEqual("equipDragDrop"u8)) return; using var tt = ImUtf8.Tooltip(); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 8cf81ff..4499107 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -91,7 +91,7 @@ public sealed unsafe class AdvancedDyePopup( var modelHandle = model == null ? null : model->ModelResourceHandle; var path = materialHandle == null ? string.Empty - : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); + : ByteString.FromSpanUnsafe(materialHandle->FileName.AsSpan(), true).ToString(); var gamePath = modelHandle == null ? string.Empty : modelHandle->GetMaterialFileNameBySlot(index.MaterialIndex).ToString(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 4b05e35..8a85a45 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -432,7 +432,7 @@ public class SetPanel( if (source) { ImUtf8.Text($"Moving design #{index + 1:D2}..."); - if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) + if (ImGui.SetDragDropPayload(dragDropLabel, null, 0)) { _dragIndex = index; _selector.DragDesignIndex = index; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index dca0ce5..8a235ae 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -144,7 +144,7 @@ public class SetSelector : IDisposable ImGui.SameLine(); var f = _enabledFilter; - if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3)) + if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3u)) { _enabledFilter = _enabledFilter switch { @@ -347,7 +347,7 @@ public class SetSelector : IDisposable if (source) { ImGui.TextUnformatted($"Moving design set {GetSetName(set, index)} from position {index + 1}..."); - if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) + if (ImGui.SetDragDropPayload(dragDropLabel, null, 0)) _dragIndex = index; } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 9992bc3..aa94a23 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -49,7 +49,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.TableNextColumn(); var address = _drawObject.Address; ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, (nint)(&address), nint.Zero, nint.Zero, "%llx", + if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, ref address, nint.Zero, nint.Zero, "%llx", ImGuiInputTextFlags.CharsHexadecimal)) _drawObject = address; ImGuiUtil.DrawTableColumn(_penumbra.Available diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 7e5807e..d9517c8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -195,7 +195,7 @@ public class DesignLinkDrawer( { if (source) { - ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0); + ImGui.SetDragDropPayload("DraggingLink", null, 0); ImGui.TextUnformatted($"Reordering {design.Name}..."); _dragDropIndex = index; _dragDropOrder = order; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 5bb3d77..84aaac0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,6 +154,11 @@ public class DesignPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selector.Selected!)); + } } private void DrawCustomize() diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 76435ec..5c4fec3 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -123,7 +123,7 @@ public class CollectionOverrideDrawer( { if (source) { - ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); + ImGui.SetDragDropPayload("DraggingOverride", null, 0); ImGui.TextUnformatted($"Reordering Override #{idx + 1}..."); _dragDropIndex = idx; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 947f0c6..8644aeb 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -123,7 +123,7 @@ public class UnlockOverview( var unlocked = customizeUnlocks.IsUnlocked(customize, out var time); var icon = customizations.Manager.GetIcon(customize.IconId); var hasIcon = icon.TryGetWrap(out var wrap, out _); - ImGui.Image(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, + ImGui.Image(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) @@ -135,7 +135,7 @@ public class UnlockOverview( using var tt = ImRaii.Tooltip(); var size = new Vector2(wrap!.Width, wrap.Height); if (size.X >= iconSize.X && size.Y >= iconSize.Y) - ImGui.Image(wrap.ImGuiHandle, size); + ImGui.Image(wrap.Handle, size); ImGui.TextUnformatted(unlockData.Name); ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}"); ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked."); @@ -194,7 +194,7 @@ public class UnlockOverview( if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; - var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); + var (icon, size) = (iconHandle.Handle, new Vector2(iconHandle.Width, iconHandle.Height)); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); @@ -265,7 +265,7 @@ public class UnlockOverview( if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; - var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); + var (icon, size) = (iconHandle.Handle, new Vector2(iconHandle.Width, iconHandle.Height)); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 74b7800..2b55f94 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -67,7 +67,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeHook = null!; private void CrestChangeDetour(DrawDataContainer* container, byte crestFlags) diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 4190746..3b9edb7 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -114,9 +114,9 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var frame = DateTimeOffset.UtcNow.UtcTicks; var hueByte = frame % (steps * frameLength) / frameLength; var hue = (float)hueByte / steps; - float r, g, b = 0; - ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &r, &g, &b); - return new Vector3(r, g, b); + Vector3 ret; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &ret.X, &ret.Y, &ret.Z); + return ret; } public void Dispose() diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 29c2343..a0ec443 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,3 +1,4 @@ +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; @@ -12,30 +13,30 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); - public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) + public (ImTextureID, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) { if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) - return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + return (ret.Handle, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); return idx < 12 && _slotIcons[idx] != null - ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) - : (nint.Zero, Vector2.Zero, true); + ? (_slotIcons[idx]!.Handle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (default, Vector2.Zero, true); } - public (nint, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) + public (ImTextureID, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) { if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) - return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + return (ret.Handle, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); if (idx == uint.MaxValue) - return (nint.Zero, Vector2.Zero, true); + return (default, Vector2.Zero, true); idx += 12; return idx < 13 && _slotIcons[idx] != null - ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) - : (nint.Zero, Vector2.Zero, true); + ? (_slotIcons[idx]!.Handle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (default, Vector2.Zero, true); } public void Dispose() From 44729205368908f17e3d0660fe7779741122e0a4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:46:24 +0200 Subject: [PATCH 153/212] Move Viera Ears sig to gamedata. --- Glamourer/Interop/VieraEarService.cs | 3 ++- Penumbra.GameData | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs index 1e5c4eb..a6afd1d 100644 --- a/Glamourer/Interop/VieraEarService.cs +++ b/Glamourer/Interop/VieraEarService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Penumbra.GameData; using Penumbra.GameData.Interop; namespace Glamourer.Interop; @@ -69,7 +70,7 @@ public unsafe class VieraEarService : IDisposable private unsafe Hook Create() { - var hook = _interop.HookFromSignature("E8 ?? ?? ?? ?? 48 8D 8F ?? ?? ?? ?? 4C 8D 4C 24", SetupVieraEarDetour); + var hook = _interop.HookFromSignature(Sigs.SetupVieraEars, SetupVieraEarDetour); hook.Enable(); return hook; } diff --git a/Penumbra.GameData b/Penumbra.GameData index 17f2f49..7981d20 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 17f2f496664b0d69ebd7fcdabe7bc8e3e20b6463 +Subproject commit 7981d2041bc49096101ab128682155dbe1fbc468 From 97e32a3cb7a953eba65bcbca0ee4040d76a0f1da Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:48:19 +0200 Subject: [PATCH 154/212] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 7981d20..ea49bc0 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 7981d2041bc49096101ab128682155dbe1fbc468 +Subproject commit ea49bc099e783ecafdf78f0bd0bc41fb8c60ad19 From be78f1447bd20c1c273a55ce4823780437f7e748 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:56:08 +0200 Subject: [PATCH 155/212] 1.5.0.0 --- Glamourer/Gui/GlamourerChangelog.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a62743c..31d1ce3 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -43,6 +43,7 @@ public class GlamourerChangelog Add1_3_7_0(Changelog); Add1_3_8_0(Changelog); Add1_4_0_0(Changelog); + Add1_5_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -63,6 +64,19 @@ public class GlamourerChangelog } } + private static void Add1_5_0_0(Changelog log) + => log.NextVersion("Version 1.5.0.0") + .RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.") + .RegisterHighlight("Added the new Viera Ears state to designs. Old designs will not apply the state.") + .RegisterHighlight("Added the option to make newly created designs write-protected by default to the design defaults.") + .RegisterEntry("Fixed issues with reverting state and IPC.") + .RegisterEntry("Fixed an issue when using the mousewheel to scroll through designs (1.4.0.3).") + .RegisterEntry("Fixed an issue with invalid bonus items (1.4.0.3).") + .RegisterHighlight("Added drag & drop of equipment pieces which will try to match the corresponding model IDs in other slots if possible (1.4.0.2).") + .RegisterEntry("Heavily optimized some issues when having many designs and creating new ones or updating them (1.4.0.2)") + .RegisterEntry("Fixed an issue with staining templates (1.4.0.1).") + .RegisterEntry("Fixed an issue with the QDB buttons not counting correctly (1.4.0.1)."); + private static void Add1_4_0_0(Changelog log) => log.NextVersion("Version 1.4.0.0") .RegisterHighlight("The design selector width is now draggable within certain restrictions that depend on the total window width.") From b66df624f77b7a765f6c4739627d1645422e74a5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:01:54 +0200 Subject: [PATCH 156/212] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index ea49bc0..fd875c4 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ea49bc099e783ecafdf78f0bd0bc41fb8c60ad19 +Subproject commit fd875c43ee910350107b2609809335285bd4ac0f From 56753ae7bab53ce93ea9e150d569f7b46e8c1e99 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:03:58 +0200 Subject: [PATCH 157/212] Use staging for release. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 557cbf23ce7cf431f87643738092bb1b9d334b98 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 8 Aug 2025 21:06:10 +0000 Subject: [PATCH 158/212] [CI] Updating repo.json for 1.5.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 0c6e22f..3f9f19c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.3", - "TestingAssemblyVersion": "1.4.0.3", + "AssemblyVersion": "1.5.0.0", + "TestingAssemblyVersion": "1.5.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 98574558e5b3fdbbebc9368c6f2033543956294a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:07:08 +0200 Subject: [PATCH 159/212] Set Repo API level to 13 and remove stg from future releases. --- .github/workflows/release.yml | 2 +- repo.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/repo.json b/repo.json index 3f9f19c..40cbed2 100644 --- a/repo.json +++ b/repo.json @@ -21,8 +21,8 @@ "TestingAssemblyVersion": "1.5.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 12, - "TestingDalamudApiLevel": 12, + "DalamudApiLevel": 13, + "TestingDalamudApiLevel": 13, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From a8b79993df3200c196e53ea92177558b1e9ef56f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:47:11 +0200 Subject: [PATCH 160/212] Make QDB ignore close hotkey. --- Glamourer/Gui/DesignQuickBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 2dee0e4..6425c5c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -52,7 +52,6 @@ public sealed class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) - : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; @@ -64,6 +63,7 @@ public sealed class DesignQuickBar : Window, IDisposable IsOpen = _config.Ephemeral.ShowDesignQuickBar; DisableWindowSounds = true; Size = Vector2.Zero; + RespectCloseHotkey = false; } public void Dispose() From 52fd29c4787255067aa118fa9fbe1a8080e8ab82 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:52:17 +0200 Subject: [PATCH 161/212] Woops --- Glamourer/Gui/DesignQuickBar.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 6425c5c..e8c0ce3 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -52,6 +52,7 @@ public sealed class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; From e83f328cdcc306d2b2ef0af7332629bc75c33a39 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:58:52 +0200 Subject: [PATCH 162/212] Fix resizable child. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 9523b7a..539ce9e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9523b7ac725656b21fa98faef96962652e86e64f +Subproject commit 539ce9e504fdc8bb0c2ca229905f4d236c376f6a From 0d94aae7322b5baf2a0f50b03cc561b52f60e4ac Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 12:11:42 +0200 Subject: [PATCH 163/212] Fix popups not working early. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 539ce9e..5224ac5 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 539ce9e504fdc8bb0c2ca229905f4d236c376f6a +Subproject commit 5224ac538b1a7c0e86e7d2ceaf652d8d807888ae From 4761b8f5847ddd246703437f821886007cae476e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 13:01:48 +0200 Subject: [PATCH 164/212] Need staging again... --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 34bf95dddb739d10741122bbe76d01e178bcc957 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 11:03:48 +0000 Subject: [PATCH 165/212] [CI] Updating repo.json for 1.5.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 40cbed2..ffa61be 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.0", - "TestingAssemblyVersion": "1.5.0.0", + "AssemblyVersion": "1.5.0.1", + "TestingAssemblyVersion": "1.5.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a9caddafd50c0e223fd191f4decdc6352aa8aad1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 18:39:07 +0200 Subject: [PATCH 166/212] Maybe fix design crashes. --- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 7341d31..e0e4543 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -175,7 +175,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Date: Sat, 9 Aug 2025 16:41:32 +0000 Subject: [PATCH 167/212] [CI] Updating repo.json for 1.5.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ffa61be..56d0c96 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.1", - "TestingAssemblyVersion": "1.5.0.1", + "AssemblyVersion": "1.5.0.2", + "TestingAssemblyVersion": "1.5.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 8f34f197d0aa70693c8ccf98911778b397cc2729 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 18:53:22 +0200 Subject: [PATCH 168/212] Another try. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5224ac5..5e32bb2 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5224ac538b1a7c0e86e7d2ceaf652d8d807888ae +Subproject commit 5e32bb225e5fbb60ed8426ed887fd7e8a831ebae From 304b362002cafbaf97689d51105644ced66157a8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 16:55:27 +0000 Subject: [PATCH 169/212] [CI] Updating repo.json for 1.5.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 56d0c96..e117eee 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.2", - "TestingAssemblyVersion": "1.5.0.2", + "AssemblyVersion": "1.5.0.3", + "TestingAssemblyVersion": "1.5.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 612cd31c3e86eafa8d81077cb20882ce5e709523 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 19:02:26 +0200 Subject: [PATCH 170/212] Fix some caravans. --- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 042f2a2..8a3dd06 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -189,10 +189,7 @@ public class DesignDetailTab else if (_selector.Selected!.Color.Length != 0) { ImGui.SameLine(); - var size = new Vector2(ImGui.GetFrameHeight()); - using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); - ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); + ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, "The color associated with this design does not exist."u8, _colors.MissingColor); } ImUtf8.DrawFrameColumn("Creation Date"u8); From 240c889fff459afd0769a92123e93c815f386df2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 20:48:46 +0200 Subject: [PATCH 171/212] Fix changed equipment access to use new size. --- Glamourer/Interop/Material/MaterialManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 9eccb29..1ffd820 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,7 +197,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); - return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0); + return ((CharacterArmor*)human->ChangedEquipData + slotId * 4)->ToWeapon(0); } /// From e4374337f2ea3099da8fc06d84b0f02a5ad524ff Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 18:50:50 +0000 Subject: [PATCH 172/212] [CI] Updating repo.json for 1.5.0.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index e117eee..447bda2 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.3", - "TestingAssemblyVersion": "1.5.0.3", + "AssemblyVersion": "1.5.0.4", + "TestingAssemblyVersion": "1.5.0.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 26862ba78f46e69d7d3e1ab48c795e60c1ef7b0b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 19:58:36 +0200 Subject: [PATCH 173/212] Update ChangedEquipData. --- Glamourer/Interop/Material/MaterialManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 1ffd820..5b731b0 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,7 +197,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); - return ((CharacterArmor*)human->ChangedEquipData + slotId * 4)->ToWeapon(0); + var item = (ChangedEquipData*)human->ChangedEquipData + slotId; + return ((CharacterArmor*)item)->ToWeapon(0); } /// From dc431c10a55f32894af23aff9650ae94d0c81631 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 19:59:20 +0200 Subject: [PATCH 174/212] Add chat command to toggle automation. --- Glamourer/Services/CommandService.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index d47f26f..d2feac0 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -96,6 +96,12 @@ public class CommandService : IDisposable, IApiService _config.Ephemeral.LockMainWindow = !_config.Ephemeral.LockMainWindow; _config.Ephemeral.Save(); return; + case "automation": + var newValue = !_config.EnableAutoDesigns; + _config.EnableAutoDesigns = newValue; + _autoDesignApplier.OnEnableAutoDesignsChanged(newValue); + _config.Save(); + return; default: _chat.Print("Use without argument to toggle the main window."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer") From 4cc191cb25308629225847a3d27732e69eb34999 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 20:53:44 +0200 Subject: [PATCH 175/212] Add viera ear visibility to application rules. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 84aaac0..381d342 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -270,11 +270,9 @@ public class DesignPanel DrawCrestApplication(); ImUtf8.IconDummy(); DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); } - ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); + ImGui.SameLine(210 * ImUtf8.GlobalScale + ImGui.GetStyle().ItemSpacing.X); using (var _ = ImRaii.Group()) { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) @@ -316,6 +314,9 @@ public class DesignPanel ImUtf8.IconDummy(); DrawParameterApplication(); + + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); } } @@ -324,7 +325,7 @@ public class DesignPanel var enabled = _config.DeleteDesignModifier.IsActive(); bool? equip = null; bool? customize = null; - var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + var size = new Vector2(210 * ImUtf8.GlobalScale, 0); if (ImUtf8.ButtonEx("Disable Everything"u8, "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, !enabled)) @@ -414,6 +415,7 @@ public class DesignPanel "Apply Hat Visibility", "Apply Visor State", "Apply Weapon Visibility", + "Apply Viera Ear Visibility", ]; private void DrawMetaApplication() From abf998a7273c7bcc9ff2ec262b6bc5954f660f50 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 12 Aug 2025 12:29:55 +0200 Subject: [PATCH 176/212] Update GameData --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index fd875c4..2f5e901 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fd875c43ee910350107b2609809335285bd4ac0f +Subproject commit 2f5e901314444238ab3aa6c5043368622bca815a From 65f789880d06d94879a78d33ab39241362b8ed95 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 12 Aug 2025 10:32:03 +0000 Subject: [PATCH 177/212] [CI] Updating repo.json for 1.5.0.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 447bda2..513dd43 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.4", - "TestingAssemblyVersion": "1.5.0.4", + "AssemblyVersion": "1.5.0.5", + "TestingAssemblyVersion": "1.5.0.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c9b291c2f313a199766a410ee4711cd893907ffe Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 12 Aug 2025 14:46:56 +0200 Subject: [PATCH 178/212] Add new parameter to LoadWeapon hook. --- Glamourer/Interop/WeaponService.cs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index b0bdd19..54f318b 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -13,7 +13,7 @@ public unsafe class WeaponService : IDisposable private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); - private readonly delegate* unmanaged[Stdcall] + private readonly delegate* unmanaged[Stdcall] _original; public WeaponService(WeaponLoading @event, IGameInteropProvider interop) @@ -22,7 +22,7 @@ public unsafe class WeaponService : IDisposable _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = - (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >) + (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, int, void >) DrawDataContainer.MemberFunctionPointers.LoadWeapon; _loadWeaponHook.Enable(); } @@ -36,13 +36,14 @@ public unsafe class WeaponService : IDisposable // redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one. // skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change) // unk4 seemed to be the same as unk1. + // unk5 is new in 7.30 and is checked at the beginning of the function to call some timeline related function. private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, - byte skipGameObject, byte unk4); + byte skipGameObject, byte unk4, byte unk5); private readonly Hook _loadWeaponHook; private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2, - byte skipGameObject, byte unk4) + byte skipGameObject, byte unk4, byte unk5) { if (!_inUpdate.Value) { @@ -64,21 +65,21 @@ public unsafe class WeaponService : IDisposable else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0) _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); - _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4, unk5); if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) tmpWeapon.Stains = StainIds.None; - _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4, unk5); } Glamourer.Log.Excessive( - $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); + $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}, {unk5}"); } else { - _loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); + _loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4, unk5); } } @@ -89,18 +90,18 @@ public unsafe class WeaponService : IDisposable { case EquipSlot.MainHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; case EquipSlot.OffHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; case EquipSlot.BothHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); - _original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0); + _original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; } From 49d24df2e7e8812ce1a3b960ce355f06260b56a3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 12 Aug 2025 12:53:44 +0000 Subject: [PATCH 179/212] [CI] Updating repo.json for 1.5.0.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 513dd43..eec3de8 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.5", - "TestingAssemblyVersion": "1.5.0.5", + "AssemblyVersion": "1.5.0.6", + "TestingAssemblyVersion": "1.5.0.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e854386b2384c2f20cba1d1c149f3a70a81779f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:58:04 +0200 Subject: [PATCH 180/212] Update OtterGui --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5e32bb2..4a9b71a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5e32bb225e5fbb60ed8426ed887fd7e8a831ebae +Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 From bb2ba0cf113fa6b0b60ef2571829dccfc9c21cfc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:58:41 +0200 Subject: [PATCH 181/212] Add glasses to advanced dye slot combo. --- Glamourer/Gui/Materials/MaterialDrawer.cs | 41 +++++++++++++++---- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 2 +- .../Interop/Material/MaterialValueIndex.cs | 19 +++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index ce50ff2..7c16372 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -18,7 +18,6 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public const float GlossWidth = 100; public const float SpecularStrengthWidth = 125; - private EquipSlot _newSlot = EquipSlot.Head; private int _newMaterialIdx; private int _newRowIdx; private MaterialValueIndex _newKey = MaterialValueIndex.FromSlot(EquipSlot.Head); @@ -178,14 +177,42 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public sealed class MaterialSlotCombo; + private void DrawSlotCombo() + { + var width = ImUtf8.CalcTextSize(EquipSlot.OffHand.ToName()).X + ImGui.GetFrameHeightWithSpacing(); + ImGui.SetNextItemWidth(width); + using var combo = ImUtf8.Combo("##slot"u8, _newKey.SlotName()); + if (combo) + { + var currentSlot = _newKey.ToEquipSlot(); + foreach (var tmpSlot in EquipSlotExtensions.FullSlots) + { + if (ImUtf8.Selectable(tmpSlot.ToName(), tmpSlot == currentSlot) && currentSlot != tmpSlot) + _newKey = MaterialValueIndex.FromSlot(tmpSlot) with + { + MaterialIndex = (byte)_newMaterialIdx, + RowIndex = (byte)_newRowIdx, + }; + } + + var currentBonus = _newKey.ToBonusSlot(); + foreach (var bonusSlot in BonusExtensions.AllFlags) + { + if (ImUtf8.Selectable(bonusSlot.ToName(), bonusSlot == currentBonus) && bonusSlot != currentBonus) + _newKey = MaterialValueIndex.FromSlot(bonusSlot) with + { + MaterialIndex = (byte)_newMaterialIdx, + RowIndex = (byte)_newRowIdx, + }; + } + } + + ImUtf8.HoverTooltip("Choose a slot for an advanced dye row."u8); + } + public void DrawNew(Design design) { - if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) - _newKey = MaterialValueIndex.FromSlot(_newSlot) with - { - MaterialIndex = (byte)_newMaterialIdx, - RowIndex = (byte)_newRowIdx, - }; + DrawSlotCombo(); ImUtf8.SameLineInner(); DrawMaterialIdxDrag(); ImUtf8.SameLineInner(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index ce843c4..1d3e711 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -2,11 +2,11 @@ using Dalamud.Utility; using Dalamud.Bindings.ImGui; using OtterGui; -using OtterGui.Custom; using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; +using OtterGui.Custom; namespace Glamourer.Gui.Tabs.AutomationTab; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 712b0d5..eb3f71f 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -50,6 +50,18 @@ public readonly record struct MaterialValueIndex( return idx > 2 ? Invalid : new MaterialValueIndex(DrawObjectType.Human, (byte)(idx + 16), 0, 0); } + public string SlotName() + { + var slot = ToEquipSlot(); + if (slot is not EquipSlot.Unknown) + return slot.ToName(); + + if (DrawObject is DrawObjectType.Human && SlotIndex is 16) + return BonusItemFlag.Glasses.ToString(); + + return EquipSlot.Unknown.ToName(); + } + public EquipSlot ToEquipSlot() => DrawObject switch { @@ -59,6 +71,13 @@ public readonly record struct MaterialValueIndex( _ => EquipSlot.Unknown, }; + public BonusItemFlag ToBonusSlot() + => DrawObject switch + { + DrawObjectType.Human when SlotIndex > 15 => ((uint)SlotIndex - 16).ToBonusSlot(), + _ => BonusItemFlag.Unknown, + }; + public unsafe bool TryGetModel(Actor actor, out Model model) { if (!actor.Valid) From 22e6c0655bcb67e0ee8a6c1a6b03fbdef5729815 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:59:03 +0200 Subject: [PATCH 182/212] Add ear state when toggling meta application via button. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 381d342..e3c965c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,6 +154,7 @@ public class DesignPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } + ImGui.SameLine(); using (var _ = ImRaii.Group()) { @@ -403,6 +404,7 @@ public class DesignPanel _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.EarState, equip.Value); } if (customize.HasValue) From b2b8f2b6ebc5fa54b4051c204ae11a05aa6e716a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Aug 2025 10:43:26 +0200 Subject: [PATCH 183/212] Make glamourers visor toggle trigger static visors. (?!?) --- Glamourer/Interop/VisorService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 25823b6..83262e4 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -9,9 +9,9 @@ namespace Glamourer.Interop; public class VisorService : IDisposable { - private readonly PenumbraReloaded _penumbra; - private readonly IGameInteropProvider _interop; - public readonly VisorStateChanged Event; + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VisorStateChanged Event; public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) { @@ -36,7 +36,7 @@ public class VisorService : IDisposable /// The draw object. /// The desired state (true: toggled). /// Whether the state was changed. - public bool SetVisorState(Model human, bool on) + public unsafe bool SetVisorState(Model human, bool on) { if (!human.IsHuman) return false; @@ -46,6 +46,8 @@ public class VisorService : IDisposable if (oldState == on) return false; + // No clue what this flag does, but it's necessary for toggling static visors on or off, e.g. Alternate Cap (6229-1). + human.AsHuman->StateFlags |= (CharacterBase.StateFlag)0x40000000; SetupVisorDetour(human, human.GetArmor(EquipSlot.Head).Set.Id, on); return true; } From 3704051b0fc9f367c750ee9b636e937ddeaa3c9e Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 17 Aug 2025 08:45:55 +0000 Subject: [PATCH 184/212] [CI] Updating repo.json for 1.5.0.7 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index eec3de8..849d380 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.6", - "TestingAssemblyVersion": "1.5.0.6", + "AssemblyVersion": "1.5.0.7", + "TestingAssemblyVersion": "1.5.0.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2c34154915a242694ffca33f7684c1e349044f71 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Aug 2025 18:08:53 +0200 Subject: [PATCH 185/212] Update API. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index c27a060..0a97029 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c27a06004138f2ec80ccdb494bb6ddf6d39d2165 +Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 diff --git a/Penumbra.GameData b/Penumbra.GameData index 2f5e901..15e7c8e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2f5e901314444238ab3aa6c5043368622bca815a +Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 From fb065549e9ea65ca817b41e7cfc0e6df0aec2180 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Aug 2025 20:32:27 +0200 Subject: [PATCH 186/212] Add PCP Service. --- Glamourer/Configuration.cs | 59 ++++----- Glamourer/Glamourer.cs | 1 + Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 40 +++++- Glamourer/Interop/Penumbra/PenumbraService.cs | 63 +++++++--- Glamourer/Services/PcpService.cs | 119 ++++++++++++++++++ Glamourer/packages.lock.json | 20 +++ 6 files changed, 251 insertions(+), 51 deletions(-) create mode 100644 Glamourer/Services/PcpService.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index f128bdd..d266a55 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -40,34 +40,37 @@ public class Configuration : IPluginConfiguration, ISavable [JsonIgnore] public readonly EphemeralConfig Ephemeral; - public bool UseRestrictedGearProtection { get; set; } = false; - public bool OpenFoldersByDefault { get; set; } = false; - public bool AutoRedrawEquipOnChanges { get; set; } = false; - public bool EnableAutoDesigns { get; set; } = true; - public bool HideApplyCheckmarks { get; set; } = false; - public bool SmallEquip { get; set; } = false; - public bool UnlockedItemMode { get; set; } = false; - public byte DisableFestivals { get; set; } = 1; - public bool EnableGameContextMenu { get; set; } = true; - public bool HideWindowInCutscene { get; set; } = false; - public bool ShowAutomationSetEditing { get; set; } = true; - public bool ShowAllAutomatedApplicationRules { get; set; } = true; - public bool ShowUnlockedItemWarnings { get; set; } = true; - public bool RevertManualChangesOnZoneChange { get; set; } = false; - public bool ShowQuickBarInTabs { get; set; } = true; - public bool OpenWindowAtStart { get; set; } = false; - public bool ShowWindowWhenUiHidden { get; set; } = false; - public bool KeepAdvancedDyesAttached { get; set; } = true; - public bool ShowPalettePlusImport { get; set; } = true; - public bool UseFloatForColors { get; set; } = true; - public bool UseRgbForColors { get; set; } = true; - public bool ShowColorConfig { get; set; } = true; - public bool ChangeEntireItem { get; set; } = false; - public bool AlwaysApplyAssociatedMods { get; set; } = false; - public bool UseTemporarySettings { get; set; } = true; - public bool AllowDoubleClickToApply { get; set; } = false; - public bool RespectManualOnAutomationUpdate { get; set; } = false; - public bool PreventRandomRepeats { get; set; } = false; + public bool AttachToPcp { get; set; } = true; + public bool UseRestrictedGearProtection { get; set; } = false; + public bool OpenFoldersByDefault { get; set; } = false; + public bool AutoRedrawEquipOnChanges { get; set; } = false; + public bool EnableAutoDesigns { get; set; } = true; + public bool HideApplyCheckmarks { get; set; } = false; + public bool SmallEquip { get; set; } = false; + public bool UnlockedItemMode { get; set; } = false; + public byte DisableFestivals { get; set; } = 1; + public bool EnableGameContextMenu { get; set; } = true; + public bool HideWindowInCutscene { get; set; } = false; + public bool ShowAutomationSetEditing { get; set; } = true; + public bool ShowAllAutomatedApplicationRules { get; set; } = true; + public bool ShowUnlockedItemWarnings { get; set; } = true; + public bool RevertManualChangesOnZoneChange { get; set; } = false; + public bool ShowQuickBarInTabs { get; set; } = true; + public bool OpenWindowAtStart { get; set; } = false; + public bool ShowWindowWhenUiHidden { get; set; } = false; + public bool KeepAdvancedDyesAttached { get; set; } = true; + public bool ShowPalettePlusImport { get; set; } = true; + public bool UseFloatForColors { get; set; } = true; + public bool UseRgbForColors { get; set; } = true; + public bool ShowColorConfig { get; set; } = true; + public bool ChangeEntireItem { get; set; } = false; + public bool AlwaysApplyAssociatedMods { get; set; } = true; + public bool UseTemporarySettings { get; set; } = true; + public bool AllowDoubleClickToApply { get; set; } = false; + public bool RespectManualOnAutomationUpdate { get; set; } = false; + public bool PreventRandomRepeats { get; set; } = false; + public string PcpFolder { get; set; } = "PCP"; + public string PcpColor { get; set; } = ""; public DesignPanelFlag HideDesignPanel { get; set; } = 0; public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index f62085a..33c67d5 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -71,6 +71,7 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); + sb.Append($"> **`Attach to PCP: `** {config.AttachToPcp}\n"); sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n"); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 1ccb079..0a84adc 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -1,4 +1,5 @@ -using Dalamud.Game.ClientState.Keys; +using Dalamud.Bindings.ImGui; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; @@ -8,7 +9,8 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using Dalamud.Bindings.ImGui; +using Glamourer.Services; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; @@ -27,7 +29,8 @@ public class SettingsTab( CollectionOverrideDrawer overrides, CodeDrawer codeDrawer, Glamourer glamourer, - AutoDesignApplier autoDesignApplier) + AutoDesignApplier autoDesignApplier, + PcpService pcpService) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -89,6 +92,15 @@ public class SettingsTab( Checkbox("Auto-Reload Gear"u8, "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); + Checkbox("Attach to PCP-Handling"u8, + "Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8, + config.AttachToPcp, pcpService.Set); + var active = config.DeleteDesignModifier.IsActive(); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Delete all PCP Designs"u8, "Deletes all designs tagged with 'PCP' from the design list."u8, disabled: !active)) + pcpService.CleanPcpDesigns(); + if (!active) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.DeleteDesignModifier} while clicking."); Checkbox("Revert Manual Changes on Zone Change"u8, "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); @@ -124,6 +136,28 @@ public class SettingsTab( Checkbox("Reset Temporary Settings"u8, "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default."u8, config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v); + + var tmp = config.PcpFolder; + ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X); + if (ImUtf8.InputText("##pcpFolder"u8, ref tmp)) + config.PcpFolder = tmp; + + if (ImGui.IsItemDeactivatedAfterEdit()) + config.Save(); + + ImGuiUtil.LabeledHelpMarker("Default PCP Organizational Folder", + "The folder any designs created due to penumbra character packs are moved to on creation.\nLeave blank to import into Root."); + + tmp = config.PcpColor; + ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X); + if (ImUtf8.InputText("##pcpColor"u8, ref tmp)) + config.PcpColor = tmp; + + if (ImGui.IsItemDeactivatedAfterEdit()) + config.Save(); + + ImGuiUtil.LabeledHelpMarker("Default PCP Design Color", + "The name of the color group any designs created due to penumbra character packs are assigned.\nLeave blank for no specific color assignment."); } private void DrawInterfaceSettings() diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index d66ddc4..123e989 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Glamourer.Events; using Glamourer.State; +using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; @@ -49,6 +50,8 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _creatingCharacterBase; private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; + private readonly EventSubscriber _pcpParsed; + private readonly EventSubscriber _pcpCreated; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; @@ -101,6 +104,8 @@ public class PenumbraService : IDisposable _createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi); _creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi); _modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi); + _pcpCreated = global::Penumbra.Api.IpcSubscribers.CreatingPcp.Subscriber(pi); + _pcpParsed = global::Penumbra.Api.IpcSubscribers.ParsingPcp.Subscriber(pi); Reattach(); } @@ -135,6 +140,18 @@ public class PenumbraService : IDisposable remove => _modSettingChanged.Event -= value; } + public event Action PcpCreated + { + add => _pcpCreated.Event += value; + remove => _pcpCreated.Event -= value; + } + + public event Action PcpParsed + { + add => _pcpParsed.Event += value; + remove => _pcpParsed.Event -= value; + } + public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; @@ -514,28 +531,30 @@ public class PenumbraService : IDisposable _clickSubscriber.Enable(); _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); + _pcpCreated.Enable(); + _pcpParsed.Enable(); _modSettingChanged.Enable(); - _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); - _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); - _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); - _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); - _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); - _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); - _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); - _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); - _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); - _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); - _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); - _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); - _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); - _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); - _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); - _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); + _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); + _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); + _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); + _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); + _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); + _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); + _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); + _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); + _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); + _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); + _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); + _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); + _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); + _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); + _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); - _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); + _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); @@ -566,6 +585,8 @@ public class PenumbraService : IDisposable _creatingCharacterBase.Disable(); _createdCharacterBase.Disable(); _modSettingChanged.Disable(); + _pcpCreated.Disable(); + _pcpParsed.Disable(); if (Available) { _collectionByIdentifier = null; @@ -612,5 +633,7 @@ public class PenumbraService : IDisposable _initializedEvent.Dispose(); _disposedEvent.Dispose(); _modSettingChanged.Dispose(); + _pcpCreated.Dispose(); + _pcpParsed.Dispose(); } } diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs new file mode 100644 index 0000000..3894981 --- /dev/null +++ b/Glamourer/Services/PcpService.cs @@ -0,0 +1,119 @@ +using Glamourer.Designs; +using Glamourer.Interop.Penumbra; +using Glamourer.State; +using Newtonsoft.Json.Linq; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; + +namespace Glamourer.Services; + +public class PcpService : IRequiredService +{ + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly ActorObjectManager _objects; + private readonly StateManager _state; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + + public PcpService(Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, + DesignConverter designConverter, DesignManager designManager) + { + _config = config; + _penumbra = penumbra; + _objects = objects; + _state = state; + _designConverter = designConverter; + _designManager = designManager; + + _config.AttachToPcp = !_config.AttachToPcp; + Set(!_config.AttachToPcp); + } + + public void CleanPcpDesigns() + { + var designs = _designManager.Designs.Where(d => d.Tags.Contains("PCP")).ToList(); + Glamourer.Log.Information($"[PCPService] Deleting {designs.Count} designs containing the tag PCP."); + foreach (var design in designs) + _designManager.Delete(design); + } + + public void Set(bool value) + { + if (value == _config.AttachToPcp) + return; + + _config.AttachToPcp = value; + _config.Save(); + if (value) + { + Glamourer.Log.Information("[PCPService] Attached to PCP handling."); + _penumbra.PcpCreated += OnPcpCreation; + _penumbra.PcpParsed += OnPcpParse; + } + else + { + Glamourer.Log.Information("[PCPService] Detached from PCP handling."); + _penumbra.PcpCreated -= OnPcpCreation; + _penumbra.PcpParsed -= OnPcpParse; + } + } + + private void OnPcpParse(JObject jObj, string modDirectory, Guid collection) + { + Glamourer.Log.Debug("[PCPService] Parsing PCP file."); + if (jObj["Glamourer"] is not JObject glamourer) + return; + + if (glamourer["Version"]!.ToObject() is not 1) + return; + + if (_designConverter.FromJObject(glamourer["Design"] as JObject, true, true) is not { } designBase) + return; + + var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); + if (!actorIdentifier.IsValid) + return; + + var time = new DateTimeOffset(jObj["Time"]?.ToObject() ?? DateTime.UtcNow); + var design = _designManager.CreateClone(designBase, + $"{_config.PcpFolder}/{actorIdentifier} - {jObj["Note"]?.ToObject() ?? string.Empty}", true); + _designManager.AddTag(design, "PCP"); + _designManager.SetWriteProtection(design, true); + _designManager.AddMod(design, new Mod(modDirectory, modDirectory), new ModSettings([], 0, true, false, false)); + _designManager.ChangeDescription(design, $"PCP design created for {actorIdentifier} on {time}."); + _designManager.ChangeResetAdvancedDyes(design, true); + _designManager.SetQuickDesign(design, false); + _designManager.ChangeColor(design, _config.PcpColor); + + Glamourer.Log.Debug("[PCPService] Created PCP design."); + if (_state.GetOrCreate(actorIdentifier, _objects.TryGetValue(actorIdentifier, out var data) ? data.Objects[0] : Actor.Null, + out var state)) + { + _state.ApplyDesign(state!, design, ApplySettings.Manual); + Glamourer.Log.Debug($"[PCPService] Applied PCP design to {actorIdentifier.Incognito(null)}"); + } + } + + private void OnPcpCreation(JObject jObj, ushort index) + { + Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file."); + var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); + if (!actorIdentifier.IsValid) + return; + + if (!_state.GetOrCreate(actorIdentifier, _objects.Objects[(int)index], out var state)) + { + Glamourer.Log.Debug($"[PCPService] Could not get or create state for actor {index}."); + return; + } + + var design = _designConverter.Convert(state, ApplicationRules.All); + jObj["Glamourer"] = new JObject() + { + ["Version"] = 1, + ["Design"] = design.JsonSerialize(), + }; + } +} diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index f66e6e4..9e36276 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -24,6 +24,19 @@ "Vortice.DXGI": "3.4.2-beta" } }, + "FlatSharp.Compiler": { + "type": "Transitive", + "resolved": "7.9.0", + "contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A==" + }, + "FlatSharp.Runtime": { + "type": "Transitive", + "resolved": "7.9.0", + "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, "JetBrains.Annotations": { "type": "Transitive", "resolved": "2024.3.0", @@ -55,6 +68,11 @@ "SharpGen.Runtime": "2.1.2-beta" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, "Vortice.DirectX": { "type": "Transitive", "resolved": "3.4.2-beta", @@ -95,6 +113,8 @@ "penumbra.gamedata": { "type": "Project", "dependencies": { + "FlatSharp.Compiler": "[7.9.0, )", + "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", "Penumbra.Api": "[5.10.0, )", "Penumbra.String": "[1.0.6, )" From 4d4e4669dd30ca0e5029972bb70f7da6a13d4f6a Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 22 Aug 2025 18:34:49 +0000 Subject: [PATCH 187/212] [CI] Updating repo.json for testing_1.5.0.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 849d380..a40f1ff 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.7", + "TestingAssemblyVersion": "1.5.0.8", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 487d3b9399f3065239bf89ffaaca396c418d87d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Aug 2025 15:49:24 +0200 Subject: [PATCH 188/212] Update PCP Service. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 4 ++-- Glamourer/Services/PcpService.cs | 4 ++-- Penumbra.Api | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 123e989..4d70a3f 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -51,7 +51,7 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; private readonly EventSubscriber _pcpParsed; - private readonly EventSubscriber _pcpCreated; + private readonly EventSubscriber _pcpCreated; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; @@ -140,7 +140,7 @@ public class PenumbraService : IDisposable remove => _modSettingChanged.Event -= value; } - public event Action PcpCreated + public event Action PcpCreated { add => _pcpCreated.Event += value; remove => _pcpCreated.Event -= value; diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs index 3894981..3363172 100644 --- a/Glamourer/Services/PcpService.cs +++ b/Glamourer/Services/PcpService.cs @@ -96,7 +96,7 @@ public class PcpService : IRequiredService } } - private void OnPcpCreation(JObject jObj, ushort index) + private void OnPcpCreation(JObject jObj, ushort index, string path) { Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file."); var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); @@ -110,7 +110,7 @@ public class PcpService : IRequiredService } var design = _designConverter.Convert(state, ApplicationRules.All); - jObj["Glamourer"] = new JObject() + jObj["Glamourer"] = new JObject { ["Version"] = 1, ["Design"] = design.JsonSerialize(), diff --git a/Penumbra.Api b/Penumbra.Api index 0a97029..297941b 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 +Subproject commit 297941bc22300f4a8368f4d0177f62943eb69727 From 3eabe591dfb3e46b02e699f6e6381936961f3fb3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 24 Aug 2025 13:59:02 +0000 Subject: [PATCH 189/212] [CI] Updating repo.json for testing_1.5.0.9 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a40f1ff..02c85f5 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.8", + "TestingAssemblyVersion": "1.5.0.9", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.8/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.9/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 389a8781d6007865891c548db2184aa157ee2b18 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 25 Aug 2025 10:39:32 +0200 Subject: [PATCH 190/212] Update library. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 297941b..af41b17 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 297941bc22300f4a8368f4d0177f62943eb69727 +Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 From 835ba23935e9a785076bc98a8514776cc8eada5a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 25 Aug 2025 10:43:14 +0200 Subject: [PATCH 191/212] 1.5.1.0 --- Glamourer/Gui/GlamourerChangelog.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 31d1ce3..686d4a1 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -44,6 +44,7 @@ public class GlamourerChangelog Add1_3_8_0(Changelog); Add1_4_0_0(Changelog); Add1_5_0_0(Changelog); + Add1_5_1_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -64,6 +65,16 @@ public class GlamourerChangelog } } + private static void Add1_5_1_0(Changelog log) + => log.NextVersion("Version 1.5.1.0") + .RegisterHighlight("Added support for Penumbras PCP functionality to add the current state of the character as a design.") + .RegisterEntry("On import, a design for the PCP is created and, if possible, applied to the character.", 1) + .RegisterEntry("No automation is assigned.", 1) + .RegisterEntry("Finer control about this can be found in the settings.", 1) + .RegisterEntry("Fixed an issue with static visors not toggling through Glamourer (1.5.0.7).") + .RegisterEntry("The advanced dye slot combo now contains glasses (1.5.0.7).") + .RegisterEntry("Several fixes for patch-related issues (1.5.0.1 - 1.5.0.6"); + private static void Add1_5_0_0(Changelog log) => log.NextVersion("Version 1.5.0.0") .RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.") From 654787fa0d536ec3d69114bd697cb6fe496ce060 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 25 Aug 2025 08:45:28 +0000 Subject: [PATCH 192/212] [CI] Updating repo.json for 1.5.1.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 02c85f5..ab6a415 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.9", + "AssemblyVersion": "1.5.1.0", + "TestingAssemblyVersion": "1.5.1.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.9/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6e62905fa70ef5851fb01859b887310c318ef269 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 26 Aug 2025 11:54:00 +0200 Subject: [PATCH 193/212] Fix staging incompatibility with CS. --- Glamourer/Interop/Material/MaterialService.cs | 4 ++-- Glamourer/Interop/Material/PrepareColorSet.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index a5f2b36..e232798 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -69,9 +69,9 @@ public static unsafe class MaterialService return null; var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; - if (material == null || material->ColorTable == null) + if (material == null || material->DataSet == null) return null; - return (ColorTable.Table*)material->ColorTable; + return (ColorTable.Table*)material->DataSet; } } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index f52bb68..21a731b 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -69,13 +69,13 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->ColorTable == null) + if (material->DataSet == null) { table = default; return false; } - var newTable = *(ColorTable.Table*)material->ColorTable; + var newTable = *(ColorTable.Table*)material->DataSet; if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) From 889f01a7243b0c6af7b3483947ea2cea8b01add4 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 26 Aug 2025 09:58:08 +0000 Subject: [PATCH 194/212] [CI] Updating repo.json for 1.5.1.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ab6a415..78132bf 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.0", - "TestingAssemblyVersion": "1.5.1.0", + "AssemblyVersion": "1.5.1.1", + "TestingAssemblyVersion": "1.5.1.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 8e1745d67aa87a80a5491be8477a3ea26308d985 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Aug 2025 18:47:51 +0200 Subject: [PATCH 195/212] Once more with feeling --- Glamourer/Interop/Material/MaterialService.cs | 3 +-- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index e232798..4893e14 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -1,6 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Lumina.Data.Files; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; @@ -69,7 +68,7 @@ public static unsafe class MaterialService return null; var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; - if (material == null || material->DataSet == null) + if (material == null || material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) return null; return (ColorTable.Table*)material->DataSet; diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 21a731b..821a152 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -69,7 +69,7 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->DataSet == null) + if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) { table = default; return false; From 414bd8bee7d1d738290014d726b730baae0f8385 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 28 Aug 2025 16:52:43 +0000 Subject: [PATCH 196/212] [CI] Updating repo.json for 1.5.1.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 78132bf..9f7e922 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.1", - "TestingAssemblyVersion": "1.5.1.1", + "AssemblyVersion": "1.5.1.2", + "TestingAssemblyVersion": "1.5.1.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0a9693daea99f79c44b2a69e1bfb006573a721a0 Mon Sep 17 00:00:00 2001 From: Ottermandias <70807659+Ottermandias@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:29:13 +0200 Subject: [PATCH 197/212] Update CodeService.cs --- Glamourer/Services/CodeService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index af2e88b..4a82f0e 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -50,7 +50,8 @@ public class CodeService | CodeFlag.OopsMiqote | CodeFlag.OopsRoegadyn | CodeFlag.OopsAuRa - | CodeFlag.OopsHrothgar; + | CodeFlag.OopsHrothgar + | CodeFlag.OopsViera; public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; @@ -250,3 +251,4 @@ public class CodeService _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } + From c3469a1687285f5278182600877201b76b9b3d97 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 28 Sep 2025 23:55:44 +0200 Subject: [PATCH 198/212] Fix facewear advanced dyes, fix backup service not running in task, update libraries. --- Glamourer.Api | 2 +- Glamourer/Designs/DesignManager.cs | 1 - Glamourer/Glamourer.csproj | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 1 - Glamourer/Interop/Material/MaterialManager.cs | 23 ++++++++++++++----- Glamourer/Services/BackupService.cs | 10 ++++++-- Glamourer/packages.lock.json | 6 ++--- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 11 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 54c1944..7e8505c 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e +Subproject commit 7e8505cd6f8dbc5bcf41b72e16785d62b4d218f3 diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 3ddfefd..e3648a4 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,7 +8,6 @@ using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 86ae713..d7e62a9 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1cdb171..4c7c103 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,7 +3,6 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 5b731b0..43e500b 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -62,7 +62,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var drawData = type switch { - MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), + MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId), _ => GetTempSlot((Weapon*)characterBase), }; var mode = PrepareColorSet.GetMode(material); @@ -192,13 +192,24 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } /// We need to get the temporary set, variant and stain that is currently being set if it is available. - private static CharacterWeapon GetTempSlot(Human* human, byte slotId) + private static CharacterWeapon GetTempSlot(Human* human, HumanSlot slotId) { - if (human->ChangedEquipData == null) - return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); + if (human->ChangedEquipData is null) + return slotId.ToSpecificEnum() switch + { + EquipSlot slot => ((Model)human).GetArmor(slot).ToWeapon(0), + BonusItemFlag bonus => ((Model)human).GetBonus(bonus).ToWeapon(0), + _ => default, + }; - var item = (ChangedEquipData*)human->ChangedEquipData + slotId; - return ((CharacterArmor*)item)->ToWeapon(0); + if (!slotId.ToSlotIndex(out var index)) + return default; + + var item = (ChangedEquipData*)human->ChangedEquipData + index; + if (index < 10) + return ((CharacterArmor*)item)->ToWeapon(0); + + return new CharacterWeapon(item->BonusModel, 0, item->BonusVariant, StainIds.None); } /// diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index 3abf13a..511cca6 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,9 +1,10 @@ using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; namespace Glamourer.Services; -public class BackupService +public class BackupService : IAsyncService { private readonly Logger _logger; private readonly DirectoryInfo _configDirectory; @@ -14,7 +15,7 @@ public class BackupService _logger = logger; _fileNames = GlamourerFiles(fileNames); _configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); - Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames); + Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames)); } /// Create a permanent backup with a given name for migrations. @@ -40,4 +41,9 @@ public class BackupService return list; } + + public Task Awaiter { get; } + + public bool Finished + => Awaiter.IsCompletedSuccessfully; } diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index 9e36276..8ac1fe4 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[13.0.0, )", - "resolved": "13.0.0", - "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" + "requested": "[13.1.0, )", + "resolved": "13.1.0", + "contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", diff --git a/OtterGui b/OtterGui index 4a9b71a..f354444 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 +Subproject commit f354444776591ae423e2d8374aae346308d81424 diff --git a/Penumbra.Api b/Penumbra.Api index af41b17..648b6fc 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 +Subproject commit 648b6fc2ce600a95ab2b2ced27e1639af2b04502 diff --git a/Penumbra.GameData b/Penumbra.GameData index 15e7c8e..a34f314 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 +Subproject commit a34f314cbc1053a09923a0d64aa3173044d32101 diff --git a/Penumbra.String b/Penumbra.String index 878acce..c8611a0 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd +Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793 From 76ed347cbfbe9db2a314e9d42a24f9a3917a613a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:28:18 +0200 Subject: [PATCH 199/212] Update signatures. --- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 ++ Penumbra.GameData | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 02f00db..587fe65 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -71,6 +71,8 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawApplyAllButton() { var (id, name) = penumbra.CurrentCollection; + if (config.Ephemeral.IncognitoMode) + name = id.ShortGuid(); if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) ApplyAll(); diff --git a/Penumbra.GameData b/Penumbra.GameData index a34f314..7e7d510 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a34f314cbc1053a09923a0d64aa3173044d32101 +Subproject commit 7e7d510a2ce78e2af78312a8b2215c23bf43a56f From ace3a8f755f61c71c67df5f3718c6d39d2d8bb22 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:43:40 +0200 Subject: [PATCH 200/212] Again. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 7e7d510..3baace7 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 7e7d510a2ce78e2af78312a8b2215c23bf43a56f +Subproject commit 3baace73c828271dcb71a8156e3e7b91e1dd12ae From e644b8da289002dd26b51bd638c70f856e7b031b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:53:55 +0200 Subject: [PATCH 201/212] Fix span issue. --- Glamourer/Designs/DesignManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index e3648a4..e568218 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -6,11 +6,13 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; +using OtterGui.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; + namespace Glamourer.Designs; public sealed class DesignManager : DesignEditor @@ -227,7 +229,7 @@ public sealed class DesignManager : DesignEditor design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; - var idx = design.Tags.IndexOf(tag); + var idx = design.Tags.AsEnumerable().IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); @@ -260,7 +262,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, - new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag))); } /// Add an associated mod to a design. From 4228fc1b896a5bd4421f7327047bdb65365ce703 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:59:39 +0200 Subject: [PATCH 202/212] fu --- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 4c7c103..a68c191 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using Dalamud.Bindings.ImGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; @@ -489,7 +490,7 @@ public class MultiDesignPanel( foreach (var leaf in selector.SelectedPaths.OfType()) { - var index = leaf.Value.Tags.IndexOf(_tag); + var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); if (index >= 0) _removeDesigns.Add((leaf.Value, index)); else From a56852f918b718a1b1643dc130ae6ac94e111b87 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 7 Oct 2025 11:02:02 +0000 Subject: [PATCH 203/212] [CI] Updating repo.json for 1.5.1.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 9f7e922..55e372c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.2", - "TestingAssemblyVersion": "1.5.1.2", + "AssemblyVersion": "1.5.1.3", + "TestingAssemblyVersion": "1.5.1.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a0d912a395f31eab5923815bd7bcca5e0774d8dd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:23:43 +0200 Subject: [PATCH 204/212] Fix issue with reverting state of unavailable actors. --- Glamourer/State/StateApplier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index c346ab1..9800445 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -385,7 +385,7 @@ public class StateApplier( var actors = ChangeMetaState(state, MetaIndex.Wetness, true); if (redraw) { - if (withLock) + if (withLock && actors.Valid) state.TempLock(); ForceRedraw(actors); } From c604d5dbe50174da8a22d5c660c777c645b0efe0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:25:54 +0200 Subject: [PATCH 205/212] Add DeletePlayerState. --- Glamourer.Api | 2 +- Glamourer/Api/ApiHelpers.cs | 19 ++++++++++++++----- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + Glamourer/Api/StateApi.cs | 25 ++++++++++++++++++++++--- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 7e8505c..59a7ab5 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 7e8505cd6f8dbc5bcf41b72e16785d62b4d218f3 +Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 45d84b9..14cff3b 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -1,13 +1,13 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; -using OtterGui; using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Api; @@ -15,14 +15,23 @@ namespace Glamourer.Api; public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal IEnumerable FindExistingStates(string actorName) + internal IEnumerable FindExistingStates(string actorName, ushort worldId = ushort.MaxValue) { if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) yield break; - foreach (var state in stateManager.Values.Where(state - => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) - yield return state; + if (worldId == WorldId.AnyWorld.Id) + { + foreach (var state in stateManager.Values.Where(state + => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) + yield return state; + } + else + { + var identifier = actors.CreatePlayer(byteString, worldId); + if (stateManager.TryGetValue(identifier, out var state)) + yield return state; + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 14c0512..4bad983 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 6; + public const int CurrentApiVersionMinor = 7; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 2701f18..6019e68 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -54,6 +54,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State), + IpcSubscribers.DeletePlayerState.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 68c593b..3b0c2c5 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using StateChanged = Glamourer.Events.StateChanged; namespace Glamourer.Api; @@ -17,7 +18,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly ApiHelpers _helpers; private readonly StateManager _stateManager; private readonly DesignConverter _converter; - private readonly Configuration _config; private readonly AutoDesignApplier _autoDesigns; private readonly ActorObjectManager _objects; private readonly StateChanged _stateChanged; @@ -27,7 +27,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public StateApi(ApiHelpers helpers, StateManager stateManager, DesignConverter converter, - Configuration config, AutoDesignApplier autoDesigns, ActorObjectManager objects, StateChanged stateChanged, @@ -37,7 +36,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _helpers = helpers; _stateManager = stateManager; _converter = converter; - _config = config; _autoDesigns = autoDesigns; _objects = objects; _stateChanged = stateChanged; @@ -202,6 +200,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc DeletePlayerState(string playerName, ushort worldId, uint key) + { + var args = ApiHelpers.Args("Name", playerName, "World", worldId, "Key", key); + var states = _helpers.FindExistingStates(playerName).ToList(); + if (states.Count is 0) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + var anyLocked = false; + foreach (var state in states) + { + if (state.CanUnlock(key)) + _stateManager.DeleteState(state.Identifier); + else + anyLocked = true; + } + + return ApiHelpers.Return(anyLocked + ? GlamourerApiEc.InvalidKey + : GlamourerApiEc.Success, args); + } + public int UnlockAll(uint key) => _stateManager.Values.Count(state => state.Unlock(key)); From bef1e39ac34c5b08bb17c0b2075234d3ee282f55 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:36:03 +0200 Subject: [PATCH 206/212] Update Libraries. --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index f354444..a63f673 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f354444776591ae423e2d8374aae346308d81424 +Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 diff --git a/Penumbra.Api b/Penumbra.Api index 648b6fc..c23ee05 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 648b6fc2ce600a95ab2b2ced27e1639af2b04502 +Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669 diff --git a/Penumbra.GameData b/Penumbra.GameData index 3baace7..d889f9e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3baace73c828271dcb71a8156e3e7b91e1dd12ae +Subproject commit d889f9ef918514a46049725052d378b441915b00 From 88fe25f69e86906bd18ccd4ee4e552bdda1c4428 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 23 Oct 2025 15:40:25 +0000 Subject: [PATCH 207/212] [CI] Updating repo.json for testing_1.5.1.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 55e372c..ab87553 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.1.3", - "TestingAssemblyVersion": "1.5.1.3", + "TestingAssemblyVersion": "1.5.1.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.1.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 434a5a809e6ea4efbec7482dec17e6fbda500f11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:29:41 +0100 Subject: [PATCH 208/212] Make old backup files overwrite instead of throwing. --- Glamourer/Designs/DesignManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index e568218..92f8398 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -557,7 +557,7 @@ public sealed class DesignManager : DesignEditor try { File.Move(SaveService.FileNames.MigrationDesignFile, - Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak")); + Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"), true); Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file."); } catch (Exception ex) From 76b214c643a9bf4b3a4d1e0f2bb50c81a461123a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:29:59 +0100 Subject: [PATCH 209/212] Fix try-on interaction with Penumbra for facewear. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 62 ++++++++++++++++++-- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 8 ++- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ed6018e..dff9a6e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -22,11 +22,12 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private readonly CustomizeService _customize; private readonly GPoseService _gpose; - private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; + private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2 + BonusExtensions.AllFlags.Count]; - public IEnumerable> LastItems - => EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems) - .Select(p => new KeyValuePair(p.First, p.Second)); + public IEnumerable> LastItems + => EquipSlotExtensions.EqdpSlots.Cast().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand) + .Concat(BonusExtensions.AllFlags.Cast()).Zip(_lastItems) + .Select(p => new KeyValuePair(p.First, p.Second)); public ChangedItemType LastType { get; private set; } = ChangedItemType.None; public uint LastId { get; private set; } @@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable if (!Player()) return; + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + using (_ = !openTooltip ? null : ImRaii.Tooltip()) + { + ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); + if (glasses.Valid) + ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {glasses.Name} to current actor."); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public void ApplyItem(ActorState state, EquipItem item) { + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + if (ImGui.GetIO().KeyCtrl && glasses.Valid) + { + Glamourer.Log.Debug($"Re-Applying {glasses.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, default, state); + _stateManager.ChangeBonusItem(state, bonusSlot, glasses, ApplySettings.Manual); + } + else + { + Glamourer.Log.Debug($"Applying {item.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, item, state); + _stateManager.ChangeBonusItem(state, bonusSlot, item, ApplySettings.Manual); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable { var oldItem = state.ModelData.Item(slot); if (oldItem.Id != item.Id) - _lastItems[slot.ToIndex()] = oldItem; + last = oldItem; + } + } + + private void SetLastItem(BonusItemFlag slot, EquipItem item, ActorState state) + { + ref var last = ref _lastItems[slot.ToSlot() + 2]; + if (!item.Valid) + { + last = default; + } + else + { + var oldItem = state.ModelData.BonusItem(slot); + if (oldItem.Id != item.Id) + last = oldItem; } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index aa94a23..833ebe4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -89,7 +89,13 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.Separator(); foreach (var (slot, item) in _penumbraTooltip.LastItems) { - ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item"); + switch (slot) + { + case EquipSlot e: ImGuiUtil.DrawTableColumn($"{e.ToName()} Revert-Item"); break; + case BonusItemFlag f: ImGuiUtil.DrawTableColumn($"{f.ToName()} Revert-Item"); break; + default: ImGuiUtil.DrawTableColumn("Unk Revert-Item"); break; + } + ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None"); ImGui.TableNextColumn(); } From bf4673a1d950a78bc40928388402b6a3e658c594 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:30:11 +0100 Subject: [PATCH 210/212] Save Remove and Inherit for mod associations. --- Glamourer/Designs/Design.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 35ee3aa..848e7d6 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public new JObject JsonSerialize() { - var ret = new JObject() + var ret = new JObject { ["FileVersion"] = FileVersion, ["Identifier"] = Identifier, @@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn var ret = new JArray(); foreach (var (mod, settings) in AssociatedMods) { - var obj = new JObject() + var obj = new JObject { ["Name"] = mod.Name, ["Directory"] = mod.DirectoryName, - ["Enabled"] = settings.Enabled, }; + if (settings.Remove) + obj["Remove"] = true; + else if (settings.ForceInherit) + obj["Inherit"] = true; + else + obj["Enabled"] = settings.Enabled; if (settings.Enabled) { obj["Priority"] = settings.Priority; From aadcf771e71115cf9d93da6b275c5da341d52dcf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Nov 2025 23:06:53 +0100 Subject: [PATCH 211/212] 1.5.1.5 --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index a63f673..1459e2b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 +Subproject commit 1459e2b8f5e1687f659836709e23571235d4206c From 5b6517aae8259031c8bc9f600f5bdd431a1af3ae Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Nov 2025 22:09:08 +0000 Subject: [PATCH 212/212] [CI] Updating repo.json for 1.5.1.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ab87553..fd4c07f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.3", - "TestingAssemblyVersion": "1.5.1.4", + "AssemblyVersion": "1.5.1.5", + "TestingAssemblyVersion": "1.5.1.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.1.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ]