From c573feefecfebaa91c972266a9cea330779315d0 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 4 Apr 2024 12:04:19 +0000 Subject: [PATCH 01/45] [CI] Updating repo.json for testing_1.2.1.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 58dc1ee..d2cd3ef 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.1.2", + "TestingAssemblyVersion": "1.2.1.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 12fa14e1c6a6806f1975fd820e214ae81799adc3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 4 Apr 2024 18:17:35 +0200 Subject: [PATCH 02/45] Fix some issues with self-named items. --- Glamourer/Gui/Equipment/ItemCombo.cs | 7 ++----- Glamourer/Services/ItemManager.cs | 18 +++++++++++------- Penumbra.GameData | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 24ff582..7e298c0 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -105,14 +105,11 @@ public sealed class ItemCombo : FilterComboCache }; } - private static IReadOnlyList GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) + private static List GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) { var nothing = ItemManager.NothingItem(slot); if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list)) - return new[] - { - nothing, - }; + return [nothing]; var enumerable = list.AsEnumerable(); if (slot.IsEquipment()) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index e382573..1fc7077 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -66,12 +66,17 @@ public class ItemManager return SmallClothesItem(slot); if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) - return EquipItem.FromId(itemId); + { + item = EquipItem.FromId(itemId); + item = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) + : Identify(slot, item.PrimaryId, item.Variant); + return item; + } if (item.Type.ToSlot() != slot) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0, - 0, - 0); + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, + 0, 0, 0, 0); return item; } @@ -86,9 +91,8 @@ public class ItemManager return EquipItem.FromId(itemId); if (item.Type != type) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0, - 0, - 0); + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, + 0, 0, 0, 0); return item; } diff --git a/Penumbra.GameData b/Penumbra.GameData index 04237f8..e48a824 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 04237f8e80e2277ea99701bd240a09fcffe4db97 +Subproject commit e48a82471dc1bc7d6a2c39daa71a9d3c9a55ad03 From f6e74c06ccdbc7e215893d824abbb75c8e0d78ed Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Apr 2024 13:27:40 +0200 Subject: [PATCH 03/45] Fix inefficient fetching of mod settings. --- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index ae2a76c..0618d14 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -115,7 +115,7 @@ public class ModAssociationsTab if (ImGui.IsItemHovered()) { - var (_, newSettings) = _penumbra.GetMods().FirstOrDefault(m => m.Mod == mod); + var newSettings = _penumbra.GetModSettings(mod); if (ImGui.IsItemClicked()) updatedMod = (mod, newSettings); diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index c7297aa..c6617e7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -110,17 +110,38 @@ public unsafe class PenumbraService : IDisposable remove => _modSettingChanged.Event -= value; } + public ModSettings GetModSettings(in Mod mod) + { + if (!Available) + return ModSettings.Empty; + + try + { + var collection = _currentCollection.Invoke(ApiCollectionType.Current); + var (ec, tuple) = _getCurrentSettings.Invoke(collection, mod.DirectoryName, string.Empty, false); + if (ec is not PenumbraApiEc.Success) + return ModSettings.Empty; + + return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1) : ModSettings.Empty; + } + catch (Exception ex) + { + Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}"); + return ModSettings.Empty; + } + } + public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() { if (!Available) - return Array.Empty<(Mod Mod, ModSettings Settings)>(); + return []; try { var allMods = _getMods.Invoke(); var collection = _currentCollection.Invoke(ApiCollectionType.Current); return allMods - .Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, true))) + .Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, false))) .Where(t => t.Item3.Item1 is PenumbraApiEc.Success) .Select(t => (new Mod(t.Item2, t.Item1), !t.Item3.Item2.HasValue @@ -135,7 +156,7 @@ public unsafe class PenumbraService : IDisposable catch (Exception ex) { Glamourer.Log.Error($"Error fetching mods from Penumbra:\n{ex}"); - return Array.Empty<(Mod Mod, ModSettings Settings)>(); + return []; } } From 7091fdd80858b826dec222797cb37568e432c813 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Apr 2024 13:28:54 +0200 Subject: [PATCH 04/45] Fix API doc. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index d2a1406..fc16da9 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit d2a1406bc32f715c0687613f02e3f74caf7ceea9 +Subproject commit fc16da9976643baaf060ae92efae40bdcd7edc6d From 10e508b4e77f4109c2789fd32db63833ec683078 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Apr 2024 14:42:23 +0200 Subject: [PATCH 05/45] Fix visor service with umbrella on. --- Glamourer/Interop/VisorService.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 4487ef8..af66dd6 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -43,21 +43,22 @@ public class VisorService : IDisposable return true; } - private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on); + private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, byte on); private readonly Hook _setupVisorHook; - private void SetupVisorDetour(nint human, ushort modelId, bool on) + private void SetupVisorDetour(nint human, ushort modelId, byte value) { - var originalOn = on; + var originalOn = value != 0; + var on = originalOn; // Invoke an event that can change the requested value // and also control whether the function should be called at all. Event.Invoke(human, false, ref on); - Glamourer.Log.Excessive( - $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn})."); + Glamourer.Log.Verbose( + $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn} from {value} with {modelId})."); - SetupVisorDetour((Model)human, modelId, on); + SetupVisorDetour(human, modelId, on); } /// @@ -69,6 +70,6 @@ public class VisorService : IDisposable private unsafe void SetupVisorDetour(Model human, ushort modelId, bool on) { human.AsCharacterBase->VisorToggled = on; - _setupVisorHook.Original(human.Address, modelId, on); + _setupVisorHook.Original(human.Address, modelId, on ? (byte)1 : (byte)0); } } From d81197a40f70b9f297125d5e59482666f4293ebf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Apr 2024 14:42:33 +0200 Subject: [PATCH 06/45] Add OpenMainUi. --- Glamourer/Gui/GlamourerWindowSystem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 39f3c6d..0dfe50d 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -22,6 +22,7 @@ public class GlamourerWindowSystem : IDisposable _windowSystem.AddWindow(unlocksTab); _windowSystem.AddWindow(changelog.Changelog); _windowSystem.AddWindow(quick); + _uiBuilder.OpenMainUi += _ui.Toggle; _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.OpenConfigUi += _ui.Toggle; _uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene; @@ -30,6 +31,7 @@ public class GlamourerWindowSystem : IDisposable public void Dispose() { + _uiBuilder.OpenMainUi -= _ui.Toggle; _uiBuilder.Draw -= _windowSystem.Draw; _uiBuilder.OpenConfigUi -= _ui.Toggle; } From e35f2816e2b6d77ec33f49cba27fdc2a12ee3d0d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 5 Apr 2024 14:51:55 +0200 Subject: [PATCH 07/45] Make OpenConfigUi jump to settings. --- Glamourer/Gui/GlamourerWindowSystem.cs | 4 ++-- Glamourer/Gui/MainWindow.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 0dfe50d..6b34c78 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -24,7 +24,7 @@ public class GlamourerWindowSystem : IDisposable _windowSystem.AddWindow(quick); _uiBuilder.OpenMainUi += _ui.Toggle; _uiBuilder.Draw += _windowSystem.Draw; - _uiBuilder.OpenConfigUi += _ui.Toggle; + _uiBuilder.OpenConfigUi += _ui.OpenSettings; _uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene; _uiBuilder.DisableUserUiHide = config.ShowWindowWhenUiHidden; } @@ -33,6 +33,6 @@ public class GlamourerWindowSystem : IDisposable { _uiBuilder.OpenMainUi -= _ui.Toggle; _uiBuilder.Draw -= _windowSystem.Draw; - _uiBuilder.OpenConfigUi -= _ui.Toggle; + _uiBuilder.OpenConfigUi -= _ui.OpenSettings; } } diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 0cc8b9b..4de03c5 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -96,6 +96,12 @@ public class MainWindow : Window, IDisposable IsOpen = _config.OpenWindowAtStart; } + public void OpenSettings() + { + IsOpen = true; + SelectTab = TabType.Settings; + } + public override void PreDraw() { Flags = _config.Ephemeral.LockMainWindow From 9a52dddba3c21b667652da13305b949847e2dbaa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Apr 2024 15:21:07 +0200 Subject: [PATCH 08/45] Add design rename field to context. --- Glamourer/Configuration.cs | 2 + .../DesignTab/DesignFileSystemSelector.cs | 55 +++++++++++++++++++ Glamourer/Gui/Tabs/DesignTab/RenameField.cs | 26 +++++++++ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 30 ++++++++++ 4 files changed, 113 insertions(+) create mode 100644 Glamourer/Gui/Tabs/DesignTab/RenameField.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 896b431..ce2ed08 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -3,6 +3,7 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; +using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Services; using Newtonsoft.Json; using OtterGui; @@ -46,6 +47,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool AlwaysApplyAssociatedMods { get; set; } = false; public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + 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 ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index cfc3877..2608dd3 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -64,6 +64,8 @@ public sealed class DesignFileSystemSelector : FileSystemSelector.Leaf? leaf, bool clear, in DesignState storage = default) { base.Select(leaf, clear, storage); diff --git a/Glamourer/Gui/Tabs/DesignTab/RenameField.cs b/Glamourer/Gui/Tabs/DesignTab/RenameField.cs new file mode 100644 index 0000000..d79fb2f --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/RenameField.cs @@ -0,0 +1,26 @@ +namespace Glamourer.Gui.Tabs.DesignTab; + +public enum RenameField +{ + None, + RenameSearchPath, + RenameData, + BothSearchPathPrio, + BothDataPrio, +} + +public static class RenameFieldExtensions +{ + public static (string Name, string Desc) GetData(this RenameField value) + => value switch + { + RenameField.None => ("None", "Show no rename fields in the context menu for designs."), + RenameField.RenameSearchPath => ("Search Path", "Show only the search path / move field in the context menu for designs."), + RenameField.RenameData => ("Design Name", "Show only the design name field in the context menu for designs."), + RenameField.BothSearchPathPrio => ("Both (Focus Search Path)", + "Show both rename fields in the context menu for designs, but put the keyboard cursor on the search path field."), + RenameField.BothDataPrio => ("Both (Focus Design Name)", + "Show both rename fields in the context menu for designs, but put the keyboard cursor on the design name field"), + _ => (string.Empty, string.Empty), + }; +} diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 223a0a8..67ecda7 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -168,6 +168,7 @@ 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(); + DrawRenameSettings(); Checkbox("Auto-Open Design Folders", "Have design folders open or closed as their default state after launching.", config.OpenFoldersByDefault, v => config.OpenFoldersByDefault = v); @@ -411,4 +412,33 @@ public class SettingsTab( ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the designs tab."); } + + private void DrawRenameSettings() + { + ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); + using (var combo = ImRaii.Combo("##renameSettings", config.ShowRename.GetData().Name)) + { + if (combo) + foreach (var value in Enum.GetValues()) + { + var (name, desc) = value.GetData(); + if (ImGui.Selectable(name, config.ShowRename == value)) + { + config.ShowRename = value; + selector.SetRenameSearchPath(value); + config.Save(); + } + + ImGuiUtil.HoverTooltip(desc); + } + } + + ImGui.SameLine(); + const string tt = + "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); + } } From 43d683ac662aff398ea62f4d3ecd6ee17b95add6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Apr 2024 15:21:19 +0200 Subject: [PATCH 09/45] Fix WeaponCombo missing favorite. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 6 +-- Glamourer/Gui/Equipment/WeaponCombo.cs | 50 ++++++++++++++-------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 53c3d3e..33c5378 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -49,12 +49,12 @@ public class EquipmentDrawer foreach (var type in Enum.GetValues()) { if (type.ToSlot() is EquipSlot.MainHand) - _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log)); + _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites)); else if (type.ToSlot() is EquipSlot.OffHand) - _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log)); + _weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites)); } - _weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log)); + _weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log, favorites)); } private Vector2 _iconSize; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 17abb24..f6531a2 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,4 +1,5 @@ using Glamourer.Services; +using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -12,13 +13,15 @@ namespace Glamourer.Gui.Equipment; public sealed class WeaponCombo : FilterComboCache { - public readonly string Label; - private ItemId _currentItemId; - private float _innerWidth; + private readonly FavoriteManager _favorites; + public readonly string Label; + private ItemId _currentItem; + private float _innerWidth; - public WeaponCombo(ItemManager items, FullEquipType type, Logger log) - : base(() => GetWeapons(items, type), MouseWheelType.Control, log) + public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites) + : base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log) { + _favorites = favorites; Label = GetLabel(type); SearchByParts = true; } @@ -32,29 +35,38 @@ public sealed class WeaponCombo : FilterComboCache protected override int UpdateCurrentSelected(int currentSelected) { - if (CurrentSelection.ItemId == _currentItemId) + if (CurrentSelection.ItemId == _currentItem) return currentSelected; - CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItemId); + CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; return base.UpdateCurrentSelected(CurrentSelectionIdx); } + public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) + { + _innerWidth = innerWidth; + _currentItem = previewIdx; + return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); + } + protected override float GetFilterWidth() => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X; - public bool Draw(string previewName, ItemId previewId, float width, float innerWidth) - { - _currentItemId = previewId; - _innerWidth = innerWidth; - return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); - } protected override bool DrawSelectable(int globalIdx, bool selected) { var obj = Items[globalIdx]; var name = ToString(obj); - var ret = ImGui.Selectable(name, selected); + if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx) + { + CurrentSelectionIdx = -1; + _currentItem = obj.ItemId; + CurrentSelection = default; + } + + ImGui.SameLine(); + var ret = ImGui.Selectable(name, selected); ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant})"); @@ -70,7 +82,7 @@ public sealed class WeaponCombo : FilterComboCache private static string GetLabel(FullEquipType type) => type is FullEquipType.Unknown ? "Mainhand" : type.ToName(); - private static IReadOnlyList GetWeapons(ItemManager items, FullEquipType type) + private static IReadOnlyList GetWeapons(FavoriteManager favorites, ItemManager items, FullEquipType type) { if (type is FullEquipType.Unknown) { @@ -81,15 +93,15 @@ public sealed class WeaponCombo : FilterComboCache enumerable = enumerable.Concat(l); } - return enumerable.OrderBy(e => e.Name).ToList(); + return [.. enumerable.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)]; } if (!items.ItemData.ByType.TryGetValue(type, out var list)) - return Array.Empty(); + return []; if (type.AllowsNothing()) - return list.OrderBy(e => e.Name).Prepend(ItemManager.NothingItem(type)).ToList(); + return [ItemManager.NothingItem(type), .. list.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)]; - return list.OrderBy(e => e.Name).ToList(); + return [.. list.OrderByDescending(favorites.Contains).ThenBy(e => e.Name)]; } } From a1b40068e3729be7ad602b4c70c813d7c1e587d4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Apr 2024 15:22:45 +0200 Subject: [PATCH 10/45] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 4e06921..5de71c2 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4e06921da239788331a4527aa6a2943cf0e809fe +Subproject commit 5de71c22c03581738c25aa43d7dff10365ec7db3 From 9f276c76746460f45a79de998a3d14cac7e38d89 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:02:30 +0200 Subject: [PATCH 11/45] Update submodules. --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OtterGui b/OtterGui index 5de71c2..3460a81 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5de71c22c03581738c25aa43d7dff10365ec7db3 +Subproject commit 3460a817fc5e01a6b60eb834c3c59031938388fc diff --git a/Penumbra.Api b/Penumbra.Api index fc16da9..0c8578c 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit fc16da9976643baaf060ae92efae40bdcd7edc6d +Subproject commit 0c8578cfa12bf0591ed204fd89b30b66719f678f diff --git a/Penumbra.GameData b/Penumbra.GameData index e48a824..fe9d563 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e48a82471dc1bc7d6a2c39daa71a9d3c9a55ad03 +Subproject commit fe9d563d9845630673cf098f7a6bfbd26e600fb4 diff --git a/Penumbra.String b/Penumbra.String index 14e00f7..caa58c5 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 14e00f77d42bc677e02325660db765ef11932560 +Subproject commit caa58c5c92710e69ce07b9d736ebe2d228cb4488 From 0268546f634483b09a1fd952fdaa11e5811e203e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:07:31 +0200 Subject: [PATCH 12/45] Rework API/IPC and use new Penumbra IPC. --- Glamourer/Api/Api/IGlamourerApi.cs | 8 + Glamourer/Api/Api/IGlamourerApiBase.cs | 6 + Glamourer/Api/Api/IGlamourerApiDesigns.cs | 12 + Glamourer/Api/Api/IGlamourerApiItems.cs | 9 + Glamourer/Api/Api/IGlamourerApiState.cs | 28 ++ Glamourer/Api/ApiHelpers.cs | 124 +++++++ Glamourer/Api/DesignsApi.cs | 69 ++++ Glamourer/Api/Enums/ApplyFlag.cs | 34 ++ Glamourer/Api/Enums/GlamourerApiEc.cs | 13 + Glamourer/Api/GlamourerApi.cs | 22 ++ Glamourer/Api/GlamourerIpc.ApiVersions.cs | 25 -- Glamourer/Api/GlamourerIpc.Apply.cs | 180 ---------- Glamourer/Api/GlamourerIpc.Data.cs | 17 - Glamourer/Api/GlamourerIpc.Events.cs | 27 -- .../Api/GlamourerIpc.GetCustomization.cs | 64 ---- Glamourer/Api/GlamourerIpc.Revert.cs | 135 ------- Glamourer/Api/GlamourerIpc.Set.cs | 105 ------ Glamourer/Api/GlamourerIpc.cs | 196 ----------- Glamourer/Api/IpcProviders.cs | 56 +++ Glamourer/Api/IpcSubscribers/Designs.cs | 52 +++ Glamourer/Api/IpcSubscribers/Items.cs | 39 +++ Glamourer/Api/IpcSubscribers/PluginState.cs | 51 +++ Glamourer/Api/IpcSubscribers/State.cs | 235 +++++++++++++ Glamourer/Api/ItemsApi.cs | 82 +++++ Glamourer/Api/StateApi.cs | 328 ++++++++++++++++++ Glamourer/Designs/Design.cs | 4 +- Glamourer/Designs/DesignConverter.cs | 39 ++- Glamourer/Glamourer.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 3 +- .../DebugTab/IpcTester/DesignIpcTester.cs | 78 +++++ .../DebugTab/IpcTester/IpcTesterHelpers.cs | 60 ++++ .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 271 +++++++++++++++ .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 66 ++++ .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 184 ++++++++++ Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 244 ------------- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 63 ++-- .../Gui/Tabs/SettingsTab/CollectionCombo.cs | 36 ++ .../SettingsTab/CollectionOverrideDrawer.cs | 149 +++++--- .../Interop/Penumbra/ModSettingApplier.cs | 19 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 8 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 206 ++++++----- .../Services/CollectionOverrideService.cs | 108 ++++-- Glamourer/Services/CommandService.cs | 9 +- Glamourer/Services/ItemManager.cs | 24 +- Glamourer/Services/ServiceManager.cs | 9 +- Glamourer/State/StateListener.cs | 4 +- 46 files changed, 2270 insertions(+), 1233 deletions(-) create mode 100644 Glamourer/Api/Api/IGlamourerApi.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiBase.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiDesigns.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiItems.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiState.cs create mode 100644 Glamourer/Api/ApiHelpers.cs create mode 100644 Glamourer/Api/DesignsApi.cs create mode 100644 Glamourer/Api/Enums/ApplyFlag.cs create mode 100644 Glamourer/Api/Enums/GlamourerApiEc.cs create mode 100644 Glamourer/Api/GlamourerApi.cs delete mode 100644 Glamourer/Api/GlamourerIpc.ApiVersions.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Apply.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Data.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Events.cs delete mode 100644 Glamourer/Api/GlamourerIpc.GetCustomization.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Revert.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Set.cs delete mode 100644 Glamourer/Api/GlamourerIpc.cs create mode 100644 Glamourer/Api/IpcProviders.cs create mode 100644 Glamourer/Api/IpcSubscribers/Designs.cs create mode 100644 Glamourer/Api/IpcSubscribers/Items.cs create mode 100644 Glamourer/Api/IpcSubscribers/PluginState.cs create mode 100644 Glamourer/Api/IpcSubscribers/State.cs create mode 100644 Glamourer/Api/ItemsApi.cs create mode 100644 Glamourer/Api/StateApi.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs diff --git a/Glamourer/Api/Api/IGlamourerApi.cs b/Glamourer/Api/Api/IGlamourerApi.cs new file mode 100644 index 0000000..c28410b --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApi.cs @@ -0,0 +1,8 @@ +namespace Glamourer.Api.Api; + +public interface IGlamourerApi : IGlamourerApiBase +{ + public IGlamourerApiDesigns Designs { get; } + public IGlamourerApiItems Items { get; } + public IGlamourerApiState State { get; } +} \ No newline at end of file diff --git a/Glamourer/Api/Api/IGlamourerApiBase.cs b/Glamourer/Api/Api/IGlamourerApiBase.cs new file mode 100644 index 0000000..b52db45 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiBase.cs @@ -0,0 +1,6 @@ +namespace Glamourer.Api.Api; + +public interface IGlamourerApiBase +{ + public (int Major, int Minor) ApiVersion { get; } +} diff --git a/Glamourer/Api/Api/IGlamourerApiDesigns.cs b/Glamourer/Api/Api/IGlamourerApiDesigns.cs new file mode 100644 index 0000000..f4d7184 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiDesigns.cs @@ -0,0 +1,12 @@ +using Glamourer.Api.Enums; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiDesigns +{ + public Dictionary GetDesignList(); + + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags); + + public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags); +} diff --git a/Glamourer/Api/Api/IGlamourerApiItems.cs b/Glamourer/Api/Api/IGlamourerApiItems.cs new file mode 100644 index 0000000..25bdcee --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiItems.cs @@ -0,0 +1,9 @@ +using Glamourer.Api.Enums; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiItems +{ + public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot apiSlot, ulong itemId, byte stain, uint key, ApplyFlag flags); + public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags); +} diff --git a/Glamourer/Api/Api/IGlamourerApiState.cs b/Glamourer/Api/Api/IGlamourerApiState.cs new file mode 100644 index 0000000..2443701 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiState.cs @@ -0,0 +1,28 @@ +using Glamourer.Api.Enums; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiState +{ + public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key); + public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key); + + public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags); + + public GlamourerApiEc ApplyStateName(object state, string objectName, uint key, ApplyFlag flags); + + public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags); + public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags); + + public GlamourerApiEc UnlockState(int objectIndex, uint key); + public GlamourerApiEc UnlockStateName(string objectName, uint key); + public int UnlockAll(uint key); + + public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags); + public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags); + + public event Action? StateChanged; + + public event Action? GPoseChanged; +} diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs new file mode 100644 index 0000000..cf67912 --- /dev/null +++ b/Glamourer/Api/ApiHelpers.cs @@ -0,0 +1,124 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.State; +using OtterGui; +using OtterGui.Log; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.String; +using ObjectManager = Glamourer.Interop.ObjectManager; + +namespace Glamourer.Api; + +public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService +{ + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal IEnumerable FindExistingStates(string actorName) + { + 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state) + { + var actor = objects[objectIndex]; + var identifier = actor.GetIdentifier(actors); + if (!identifier.IsValid) + { + state = null; + return GlamourerApiEc.ActorNotFound; + } + stateManager.TryGetValue(identifier, out state); + return GlamourerApiEc.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal ActorState? FindState(int objectIndex) + { + var actor = objects[objectIndex]; + var identifier = actor.GetIdentifier(actors); + if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state)) + return state; + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags) + => (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch + { + ApplyFlag.Equipment => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, 0, CrestExtensions.All, 0), + ApplyFlag.Customization => design.TemporarilyRestrictApplication(0, CustomizeFlagExtensions.All, 0, + CustomizeParameterExtensions.All), + ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, + CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All), + _ => design.TemporarilyRestrictApplication(0, 0, 0, 0), + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static void Lock(ActorState state, uint key, ApplyFlag flags) + { + if ((flags & ApplyFlag.Lock) != 0 && key != 0) + state.Lock(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal IEnumerable FindStates(string objectName) + { + 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 + .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString) + .SelectWhere(kvp => + { + if (stateManager.ContainsKey(kvp.Key)) + return (false, null); + + var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); + return (ret, state); + })); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static GlamourerApiEc Return(GlamourerApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown") + { + if (ec is GlamourerApiEc.Success or GlamourerApiEc.NothingDone) + Glamourer.Log.Verbose($"[{name}] Called with {args}, returned {ec}."); + else + Glamourer.Log.Debug($"[{name}] Called with {args}, returned {ec}."); + return ec; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static LazyString Args(params object[] arguments) + { + if (arguments.Length == 0) + return new LazyString(() => "no arguments"); + + return new LazyString(() => + { + var sb = new StringBuilder(); + for (var i = 0; i < arguments.Length / 2; ++i) + { + sb.Append(arguments[2 * i]); + sb.Append(" = "); + sb.Append(arguments[2 * i + 1]); + sb.Append(", "); + } + + return sb.ToString(0, sb.Length - 2); + }); + } +} diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs new file mode 100644 index 0000000..16e0ca9 --- /dev/null +++ b/Glamourer/Api/DesignsApi.cs @@ -0,0 +1,69 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.State; +using OtterGui.Services; + +namespace Glamourer.Api; + +public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager) : IGlamourerApiDesigns, IApiService +{ + public Dictionary GetDesignList() + => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); + var design = designs.Designs.ByIdentifier(designId); + if (design == null) + return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args); + + if (helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + ApplyDesign(state, design, key, flags); + ApiHelpers.Lock(state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + private void ApplyDesign(ActorState state, Design 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); + + using var restrict = ApiHelpers.Restrict(design, flags); + stateManager.ApplyDesign(state, design, settings); + } + + public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Design", designId, "Name", objectName, "Key", key, "Flags", flags); + var design = designs.Designs.ByIdentifier(designId); + if (design == null) + return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args); + + var any = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(objectName)) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + ApplyDesign(state, design, key, flags); + ApiHelpers.Lock(state, key, flags); + } + + if (!any) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } +} diff --git a/Glamourer/Api/Enums/ApplyFlag.cs b/Glamourer/Api/Enums/ApplyFlag.cs new file mode 100644 index 0000000..0008e96 --- /dev/null +++ b/Glamourer/Api/Enums/ApplyFlag.cs @@ -0,0 +1,34 @@ +namespace Glamourer.Api.Enums; + +[Flags] +public enum ApplyFlag : ulong +{ + Once = 0x01, + Equipment = 0x02, + Customization = 0x04, + Lock = 0x08, +} + +public static class ApplyFlagEx +{ + public const ApplyFlag DesignDefault = ApplyFlag.Once | ApplyFlag.Equipment | ApplyFlag.Customization; + public const ApplyFlag StateDefault = ApplyFlag.Equipment | ApplyFlag.Customization | ApplyFlag.Lock; + public const ApplyFlag RevertDefault = ApplyFlag.Equipment | ApplyFlag.Customization; +} + +public enum ApiEquipSlot : byte +{ + Unknown = 0, + MainHand = 1, + OffHand = 2, + Head = 3, + Body = 4, + Hands = 5, + Legs = 7, + Feet = 8, + Ears = 9, + Neck = 10, + Wrists = 11, + RFinger = 12, + LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game. +} \ No newline at end of file diff --git a/Glamourer/Api/Enums/GlamourerApiEc.cs b/Glamourer/Api/Enums/GlamourerApiEc.cs new file mode 100644 index 0000000..086a2a5 --- /dev/null +++ b/Glamourer/Api/Enums/GlamourerApiEc.cs @@ -0,0 +1,13 @@ +namespace Glamourer.Api.Enums; + +public enum GlamourerApiEc +{ + Success, + ActorNotFound, + ActorNotHuman, + DesignNotFound, + ItemInvalid, + InvalidKey, + InvalidState, + NothingDone, +} diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs new file mode 100644 index 0000000..98461c6 --- /dev/null +++ b/Glamourer/Api/GlamourerApi.cs @@ -0,0 +1,22 @@ +using Glamourer.Api.Api; +using OtterGui.Services; + +namespace Glamourer.Api; + +public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +{ + public const int CurrentApiVersionMajor = 1; + public const int CurrentApiVersionMinor = 0; + + public (int Major, int Minor) ApiVersion + => (CurrentApiVersionMajor, CurrentApiVersionMinor); + + public IGlamourerApiDesigns Designs + => designs; + + public IGlamourerApiItems Items + => items; + + public IGlamourerApiState State + => state; +} diff --git a/Glamourer/Api/GlamourerIpc.ApiVersions.cs b/Glamourer/Api/GlamourerIpc.ApiVersions.cs deleted file mode 100644 index 25aaf57..0000000 --- a/Glamourer/Api/GlamourerIpc.ApiVersions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelApiVersion = "Glamourer.ApiVersion"; - public const string LabelApiVersions = "Glamourer.ApiVersions"; - - private readonly FuncProvider _apiVersionProvider; - private readonly FuncProvider<(int Major, int Minor)> _apiVersionsProvider; - - public static FuncSubscriber ApiVersionSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApiVersion); - - public static FuncSubscriber<(int Major, int Minor)> ApiVersionsSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApiVersions); - - public int ApiVersion() - => CurrentApiVersionMajor; - - public (int Major, int Minor) ApiVersions() - => (CurrentApiVersionMajor, CurrentApiVersionMinor); -} diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs deleted file mode 100644 index fecce92..0000000 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ /dev/null @@ -1,180 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Glamourer.Interop.Structs; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelApplyAll = "Glamourer.ApplyAll"; - public const string LabelApplyAllOnce = "Glamourer.ApplyAllOnce"; - public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter"; - public const string LabelApplyAllOnceToCharacter = "Glamourer.ApplyAllOnceToCharacter"; - public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment"; - public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter"; - public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization"; - public const string LabelApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter"; - - public const string LabelApplyAllLock = "Glamourer.ApplyAllLock"; - public const string LabelApplyAllToCharacterLock = "Glamourer.ApplyAllToCharacterLock"; - public const string LabelApplyOnlyEquipmentLock = "Glamourer.ApplyOnlyEquipmentLock"; - public const string LabelApplyOnlyEquipmentToCharacterLock = "Glamourer.ApplyOnlyEquipmentToCharacterLock"; - public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock"; - public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock"; - - public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; - public const string LabelApplyByGuidOnce = "Glamourer.ApplyByGuidOnce"; - public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; - public const string LabelApplyByGuidOnceToCharacter = "Glamourer.ApplyByGuidOnceToCharacter"; - - private readonly ActionProvider _applyAllProvider; - private readonly ActionProvider _applyAllOnceProvider; - private readonly ActionProvider _applyAllToCharacterProvider; - private readonly ActionProvider _applyAllOnceToCharacterProvider; - private readonly ActionProvider _applyOnlyEquipmentProvider; - private readonly ActionProvider _applyOnlyEquipmentToCharacterProvider; - private readonly ActionProvider _applyOnlyCustomizationProvider; - private readonly ActionProvider _applyOnlyCustomizationToCharacterProvider; - - private readonly ActionProvider _applyAllProviderLock; - private readonly ActionProvider _applyAllToCharacterProviderLock; - private readonly ActionProvider _applyOnlyEquipmentProviderLock; - private readonly ActionProvider _applyOnlyEquipmentToCharacterProviderLock; - private readonly ActionProvider _applyOnlyCustomizationProviderLock; - private readonly ActionProvider _applyOnlyCustomizationToCharacterProviderLock; - - private readonly ActionProvider _applyByGuidProvider; - private readonly ActionProvider _applyByGuidOnceProvider; - private readonly ActionProvider _applyByGuidToCharacterProvider; - private readonly ActionProvider _applyByGuidOnceToCharacterProvider; - - public static ActionSubscriber ApplyAllSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAll); - - public static ActionSubscriber ApplyAllOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllOnce); - - public static ActionSubscriber ApplyAllToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllToCharacter); - - public static ActionSubscriber ApplyAllOnceToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllOnceToCharacter); - - public static ActionSubscriber ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyEquipment); - - public static ActionSubscriber ApplyOnlyEquipmentToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyEquipmentToCharacter); - - public static ActionSubscriber ApplyOnlyCustomizationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyCustomization); - - public static ActionSubscriber ApplyOnlyCustomizationToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyCustomizationToCharacter); - - public static ActionSubscriber ApplyByGuidSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuid); - - public static ActionSubscriber ApplyByGuidOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidOnce); - - public static ActionSubscriber ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidToCharacter); - - public static ActionSubscriber ApplyByGuidOnceToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidOnceToCharacter); - - public static ActionSubscriber ApplyAllLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllLock); - - public static ActionSubscriber ApplyAllToCharacterLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllToCharacterLock); - - public void ApplyAll(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0); - - public void ApplyAllOnce(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0, true); - - public void ApplyAllToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0); - - public void ApplyAllOnceToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0, true); - - public void ApplyOnlyEquipment(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0); - - public void ApplyOnlyEquipmentToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, 0); - - public void ApplyOnlyCustomization(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, 0); - - public void ApplyOnlyCustomizationToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, 0); - - - public void ApplyAllLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, lockCode); - - public void ApplyAllToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, lockCode); - - public void ApplyOnlyEquipmentLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, lockCode); - - public void ApplyOnlyEquipmentToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, lockCode); - - public void ApplyOnlyCustomizationLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, lockCode); - - public void ApplyOnlyCustomizationToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, lockCode); - - - public void ApplyByGuid(Guid identifier, string characterName) - => ApplyDesignByGuid(identifier, FindActors(characterName), 0, false); - - public void ApplyByGuidOnce(Guid identifier, string characterName) - => ApplyDesignByGuid(identifier, FindActors(characterName), 0, true); - - public void ApplyByGuidToCharacter(Guid identifier, Character? character) - => ApplyDesignByGuid(identifier, FindActors(character), 0, false); - - public void ApplyByGuidOnceToCharacter(Guid identifier, Character? character) - => ApplyDesignByGuid(identifier, FindActors(character), 0, true); - - private void ApplyDesign(DesignBase? design, IEnumerable actors, byte version, uint lockCode, bool once = false) - { - if (design == null) - return; - - var hasModelId = version >= 3; - _objects.Update(); - foreach (var id in actors) - { - if (!_stateManager.TryGetValue(id, out var state)) - { - var data = _objects.TryGetValue(id, out var d) ? d : ActorData.Invalid; - if (!data.Valid || !_stateManager.GetOrCreate(id, data.Objects[0], out state)) - continue; - } - - if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) - { - _stateManager.ApplyDesign(state, design, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true, ResetMaterials: !once && lockCode != 0)); - state.Lock(lockCode); - } - } - } - - private void ApplyDesignByGuid(Guid identifier, IEnumerable actors, uint lockCode, bool once) - => ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode, once); -} diff --git a/Glamourer/Api/GlamourerIpc.Data.cs b/Glamourer/Api/GlamourerIpc.Data.cs deleted file mode 100644 index c981899..0000000 --- a/Glamourer/Api/GlamourerIpc.Data.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelGetDesignList = "Glamourer.GetDesignList"; - - private readonly FuncProvider<(string Name, Guid Identifier)[]> _getDesignListProvider; - - public static FuncSubscriber<(string Name, Guid Identifier)[]> GetDesignListSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetDesignList); - - public (string Name, Guid Identifier)[] GetDesignList() - => _designManager.Designs.Select(x => (x.Name.Text, x.Identifier)).ToArray(); -} diff --git a/Glamourer/Api/GlamourerIpc.Events.cs b/Glamourer/Api/GlamourerIpc.Events.cs deleted file mode 100644 index 982e4a1..0000000 --- a/Glamourer/Api/GlamourerIpc.Events.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Interop.Structs; -using Glamourer.State; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelStateChanged = "Glamourer.StateChanged"; - public const string LabelGPoseChanged = "Glamourer.GPoseChanged"; - - private readonly GPoseService _gPose; - private readonly StateChanged _stateChangedEvent; - private readonly EventProvider> _stateChangedProvider; - private readonly EventProvider _gPoseChangedProvider; - - private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null) - { - foreach (var actor in actors.Objects) - _stateChangedProvider.Invoke(type, actor.Address, new Lazy(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state)))); - } - - private void OnGPoseChanged(bool value) - => _gPoseChangedProvider.Invoke(value); -} diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs deleted file mode 100644 index d6caf45..0000000 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelGetAllCustomization = "Glamourer.GetAllCustomization"; - public const string LabelGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter"; - public const string LabelGetAllCustomizationLocked = "Glamourer.GetAllCustomizationLocked"; - public const string LabelGetAllCustomizationFromLockedCharacter = "Glamourer.GetAllCustomizationFromLockedCharacter"; - - private readonly FuncProvider _getAllCustomizationProvider; - private readonly FuncProvider _getAllCustomizationLockedProvider; - private readonly FuncProvider _getAllCustomizationFromCharacterProvider; - private readonly FuncProvider _getAllCustomizationFromLockedCharacterProvider; - - public static FuncSubscriber GetAllCustomizationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomization); - - public static FuncSubscriber GetAllCustomizationFromCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationFromCharacter); - - public static FuncSubscriber GetAllCustomizationLockedSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationLocked); - - public static FuncSubscriber GetAllCustomizationFromLockedCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationFromLockedCharacter); - - public string? GetAllCustomization(string characterName) - => GetCustomization(FindActors(characterName), 0); - - public string? GetAllCustomization(string characterName, uint lockCode) - => GetCustomization(FindActors(characterName), lockCode); - - public string? GetAllCustomizationFromCharacter(Character? character) - => GetCustomization(FindActors(character), 0); - - public string? GetAllCustomizationFromCharacter(Character? character, uint lockCode) - => GetCustomization(FindActors(character), lockCode); - - private string? GetCustomization(IEnumerable actors, uint lockCode) - { - var actor = actors.FirstOrDefault(ActorIdentifier.Invalid); - if (!actor.IsValid) - return null; - - if (!_stateManager.TryGetValue(actor, out var state)) - { - _objects.Update(); - if (!_objects.TryGetValue(actor, out var data) || !data.Valid) - return null; - if (!_stateManager.GetOrCreate(actor, data.Objects[0], out state)) - return null; - } - if (!state.CanUnlock(lockCode)) - return null; - - return _designConverter.ShareBase64(state, ApplicationRules.AllWithConfig(_config)); - } -} diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs deleted file mode 100644 index 8c289e7..0000000 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelRevert = "Glamourer.Revert"; - public const string LabelRevertCharacter = "Glamourer.RevertCharacter"; - public const string LabelRevertLock = "Glamourer.RevertLock"; - public const string LabelRevertCharacterLock = "Glamourer.RevertCharacterLock"; - public const string LabelRevertToAutomation = "Glamourer.RevertToAutomation"; - public const string LabelRevertToAutomationCharacter = "Glamourer.RevertToAutomationCharacter"; - public const string LabelUnlock = "Glamourer.Unlock"; - public const string LabelUnlockName = "Glamourer.UnlockName"; - public const string LabelUnlockAll = "Glamourer.UnlockAll"; - - private readonly ActionProvider _revertProvider; - private readonly ActionProvider _revertCharacterProvider; - - private readonly ActionProvider _revertProviderLock; - private readonly ActionProvider _revertCharacterProviderLock; - - private readonly FuncProvider _revertToAutomationProvider; - private readonly FuncProvider _revertToAutomationCharacterProvider; - - private readonly FuncProvider _unlockNameProvider; - private readonly FuncProvider _unlockProvider; - - private readonly FuncProvider _unlockAllProvider; - - public static ActionSubscriber RevertSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevert); - - public static ActionSubscriber RevertCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertCharacter); - - public static ActionSubscriber RevertLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertLock); - - public static ActionSubscriber RevertCharacterLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertCharacterLock); - - public static FuncSubscriber UnlockNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlockName); - - public static FuncSubscriber UnlockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlock); - - public static FuncSubscriber UnlockAllSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlockAll); - - public static FuncSubscriber RevertToAutomationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertToAutomation); - - public static FuncSubscriber RevertToAutomationCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertToAutomationCharacter); - - public void Revert(string characterName) - => Revert(FindActorsRevert(characterName), 0); - - public void RevertCharacter(Character? character) - => Revert(FindActors(character), 0); - - public void RevertLock(string characterName, uint lockCode) - => Revert(FindActorsRevert(characterName), lockCode); - - public void RevertCharacterLock(Character? character, uint lockCode) - => Revert(FindActors(character), lockCode); - - public bool Unlock(string characterName, uint lockCode) - => Unlock(FindActorsRevert(characterName), lockCode); - - public bool Unlock(Character? character, uint lockCode) - => Unlock(FindActors(character), lockCode); - - public int UnlockAll(uint lockCode) - { - var count = 0; - foreach (var state in _stateManager.Values) - if (state.Unlock(lockCode)) - ++count; - return count; - } - - public bool RevertToAutomation(string characterName, uint lockCode) - => RevertToAutomation(FindActorsRevert(characterName), lockCode); - - public bool RevertToAutomation(Character? character, uint lockCode) - => RevertToAutomation(FindActors(character), lockCode); - - private void Revert(IEnumerable actors, uint lockCode) - { - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - _stateManager.ResetState(state, StateSource.IpcFixed, lockCode); - } - } - - private bool Unlock(IEnumerable actors, uint lockCode) - { - var ret = false; - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - ret |= state.Unlock(lockCode); - } - - return ret; - } - - private bool RevertToAutomation(IEnumerable actors, uint lockCode) - { - var ret = false; - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - { - ret |= state.Unlock(lockCode); - if (_objects.TryGetValue(id, out var data)) - foreach (var obj in data.Objects) - { - _autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state, true); - _stateManager.ReapplyState(obj, StateSource.IpcManual); - } - } - } - - return ret; - } -} diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs deleted file mode 100644 index 93428da..0000000 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Services; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public enum GlamourerErrorCode - { - Success, - ActorNotFound, - ActorNotHuman, - ItemInvalid, - } - - public const string LabelSetItem = "Glamourer.SetItem"; - public const string LabelSetItemOnce = "Glamourer.SetItemOnce"; - public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; - public const string LabelSetItemOnceByActorName = "Glamourer.SetItemOnceByActorName"; - - - private readonly FuncProvider _setItemProvider; - private readonly FuncProvider _setItemOnceProvider; - private readonly FuncProvider _setItemByActorNameProvider; - private readonly FuncProvider _setItemOnceByActorNameProvider; - - public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItem); - - public static FuncSubscriber SetItemOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemOnce); - - public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemByActorName); - - public static FuncSubscriber SetItemOnceByActorNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemOnceByActorName); - - private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once) - { - if (itemId.Id == 0) - itemId = ItemManager.NothingId(slot); - - var item = _items.Resolve(slot, itemId); - if (!item.Valid) - return GlamourerErrorCode.ItemInvalid; - - var identifier = _actors.FromObject(character, false, false, false); - if (!identifier.IsValid) - return GlamourerErrorCode.ActorNotFound; - - if (!_stateManager.TryGetValue(identifier, out var state)) - { - _objects.Update(); - var data = _objects[identifier]; - if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) - return GlamourerErrorCode.ActorNotFound; - } - - if (!state.ModelData.IsHuman) - return GlamourerErrorCode.ActorNotHuman; - - _stateManager.ChangeEquip(state, slot, item, stainId, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key)); - return GlamourerErrorCode.Success; - } - - private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once) - { - if (itemId.Id == 0) - itemId = ItemManager.NothingId(slot); - - var item = _items.Resolve(slot, itemId); - if (!item.Valid) - return GlamourerErrorCode.ItemInvalid; - - var found = false; - _objects.Update(); - foreach (var identifier in FindActorsRevert(name).Distinct()) - { - if (!_stateManager.TryGetValue(identifier, out var state)) - { - var data = _objects[identifier]; - if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) - continue; - } - - if (!state.ModelData.IsHuman) - return GlamourerErrorCode.ActorNotHuman; - - _stateManager.ChangeEquip(state, slot, item, stainId, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key)); - found = true; - } - - return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound; - } -} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs deleted file mode 100644 index 428a5c7..0000000 --- a/Glamourer/Api/GlamourerIpc.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Automation; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Interop; -using Glamourer.Services; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; -using Penumbra.String; - -namespace Glamourer.Api; - -public sealed partial class GlamourerIpc : IDisposable -{ - public const int CurrentApiVersionMajor = 0; - public const int CurrentApiVersionMinor = 5; - - private readonly StateManager _stateManager; - private readonly ObjectManager _objects; - private readonly ActorManager _actors; - private readonly DesignConverter _designConverter; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly DesignManager _designManager; - private readonly ItemManager _items; - private readonly Configuration _config; - - public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, - DesignManager designManager, ItemManager items, Configuration config) - { - _stateManager = stateManager; - _objects = objects; - _actors = actors; - _designConverter = designConverter; - _autoDesignApplier = autoDesignApplier; - _items = items; - _config = config; - _gPose = gPose; - _stateChangedEvent = stateChangedEvent; - _designManager = designManager; - _apiVersionProvider = new FuncProvider(pi, LabelApiVersion, ApiVersion); - _apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions); - - _getAllCustomizationProvider = new FuncProvider(pi, LabelGetAllCustomization, GetAllCustomization); - _getAllCustomizationFromCharacterProvider = - new FuncProvider(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter); - _getAllCustomizationLockedProvider = new FuncProvider(pi, LabelGetAllCustomizationLocked, GetAllCustomization); - _getAllCustomizationFromLockedCharacterProvider = - new FuncProvider(pi, LabelGetAllCustomizationFromLockedCharacter, GetAllCustomizationFromCharacter); - - _applyAllProvider = new ActionProvider(pi, LabelApplyAll, ApplyAll); - _applyAllOnceProvider = new ActionProvider(pi, LabelApplyAllOnce, ApplyAllOnce); - _applyAllToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllToCharacter); - _applyAllOnceToCharacterProvider = new ActionProvider(pi, LabelApplyAllOnceToCharacter, ApplyAllOnceToCharacter); - _applyOnlyEquipmentProvider = new ActionProvider(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment); - _applyOnlyEquipmentToCharacterProvider = - new ActionProvider(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter); - _applyOnlyCustomizationProvider = new ActionProvider(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization); - _applyOnlyCustomizationToCharacterProvider = - new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacter, ApplyOnlyCustomizationToCharacter); - - _applyAllProviderLock = new ActionProvider(pi, LabelApplyAllLock, ApplyAllLock); - _applyAllToCharacterProviderLock = - new ActionProvider(pi, LabelApplyAllToCharacterLock, ApplyAllToCharacterLock); - _applyOnlyEquipmentProviderLock = new ActionProvider(pi, LabelApplyOnlyEquipmentLock, ApplyOnlyEquipmentLock); - _applyOnlyEquipmentToCharacterProviderLock = - new ActionProvider(pi, LabelApplyOnlyEquipmentToCharacterLock, ApplyOnlyEquipmentToCharacterLock); - _applyOnlyCustomizationProviderLock = - new ActionProvider(pi, LabelApplyOnlyCustomizationLock, ApplyOnlyCustomizationLock); - _applyOnlyCustomizationToCharacterProviderLock = - new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock); - - _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); - _applyByGuidOnceProvider = new ActionProvider(pi, LabelApplyByGuidOnce, ApplyByGuidOnce); - _applyByGuidToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter); - _applyByGuidOnceToCharacterProvider = - new ActionProvider(pi, LabelApplyByGuidOnceToCharacter, ApplyByGuidOnceToCharacter); - - _revertProvider = new ActionProvider(pi, LabelRevert, Revert); - _revertCharacterProvider = new ActionProvider(pi, LabelRevertCharacter, RevertCharacter); - _revertProviderLock = new ActionProvider(pi, LabelRevertLock, RevertLock); - _revertCharacterProviderLock = new ActionProvider(pi, LabelRevertCharacterLock, RevertCharacterLock); - _unlockNameProvider = new FuncProvider(pi, LabelUnlockName, Unlock); - _unlockProvider = new FuncProvider(pi, LabelUnlock, Unlock); - _unlockAllProvider = new FuncProvider(pi, LabelUnlockAll, UnlockAll); - _revertToAutomationProvider = new FuncProvider(pi, LabelRevertToAutomation, RevertToAutomation); - _revertToAutomationCharacterProvider = - new FuncProvider(pi, LabelRevertToAutomationCharacter, RevertToAutomation); - - _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); - _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); - - _setItemProvider = new FuncProvider(pi, LabelSetItem, - (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false)); - _setItemOnceProvider = new FuncProvider(pi, LabelSetItemOnce, - (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true)); - - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, - (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, false)); - _setItemOnceByActorNameProvider = new FuncProvider(pi, LabelSetItemOnceByActorName, - (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, true)); - - _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); - _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); - - _getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList); - } - - public void Dispose() - { - _apiVersionProvider.Dispose(); - _apiVersionsProvider.Dispose(); - - _getAllCustomizationProvider.Dispose(); - _getAllCustomizationLockedProvider.Dispose(); - _getAllCustomizationFromCharacterProvider.Dispose(); - _getAllCustomizationFromLockedCharacterProvider.Dispose(); - - _applyAllProvider.Dispose(); - _applyAllOnceProvider.Dispose(); - _applyAllToCharacterProvider.Dispose(); - _applyAllOnceToCharacterProvider.Dispose(); - _applyOnlyEquipmentProvider.Dispose(); - _applyOnlyEquipmentToCharacterProvider.Dispose(); - _applyOnlyCustomizationProvider.Dispose(); - _applyOnlyCustomizationToCharacterProvider.Dispose(); - _applyAllProviderLock.Dispose(); - _applyAllToCharacterProviderLock.Dispose(); - _applyOnlyEquipmentProviderLock.Dispose(); - _applyOnlyEquipmentToCharacterProviderLock.Dispose(); - _applyOnlyCustomizationProviderLock.Dispose(); - _applyOnlyCustomizationToCharacterProviderLock.Dispose(); - - _applyByGuidProvider.Dispose(); - _applyByGuidOnceProvider.Dispose(); - _applyByGuidToCharacterProvider.Dispose(); - _applyByGuidOnceToCharacterProvider.Dispose(); - - _revertProvider.Dispose(); - _revertCharacterProvider.Dispose(); - _revertProviderLock.Dispose(); - _revertCharacterProviderLock.Dispose(); - _unlockNameProvider.Dispose(); - _unlockProvider.Dispose(); - _unlockAllProvider.Dispose(); - _revertToAutomationProvider.Dispose(); - _revertToAutomationCharacterProvider.Dispose(); - - _stateChangedEvent.Unsubscribe(OnStateChanged); - _stateChangedProvider.Dispose(); - _gPose.Unsubscribe(OnGPoseChanged); - _gPoseChangedProvider.Dispose(); - - _getDesignListProvider.Dispose(); - - _setItemProvider.Dispose(); - _setItemOnceProvider.Dispose(); - _setItemByActorNameProvider.Dispose(); - _setItemOnceByActorNameProvider.Dispose(); - } - - private IEnumerable FindActors(string actorName) - { - if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) - return []; - - _objects.Update(); - return _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString); - } - - private IEnumerable FindActorsRevert(string actorName) - { - if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) - yield break; - - _objects.Update(); - foreach (var id in _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString) - .Select(i => i)) - yield return id; - - foreach (var id in _stateManager.Keys.Where(s => s.Type is IdentifierType.Player && s.PlayerName == byteString)) - yield return id; - } - - private IEnumerable FindActors(Character? character) - { - var id = _actors.FromObject(character, true, true, false); - if (!id.IsValid) - yield break; - - yield return id; - } -} diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs new file mode 100644 index 0000000..115142b --- /dev/null +++ b/Glamourer/Api/IpcProviders.cs @@ -0,0 +1,56 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using OtterGui.Services; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api; + +public sealed class IpcProviders : IDisposable, IApiService +{ + private readonly List _providers; + + private readonly EventProvider _disposedProvider; + private readonly EventProvider _initializedProvider; + + public IpcProviders(DalamudPluginInterface pi, IGlamourerApi api) + { + _disposedProvider = IpcSubscribers.Disposed.Provider(pi); + _initializedProvider = IpcSubscribers.Initialized.Provider(pi); + _providers = + [ + IpcSubscribers.ApiVersion.Provider(pi, api), + + IpcSubscribers.GetDesignList.Provider(pi, api.Designs), + IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), + IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), + + IpcSubscribers.SetItem.Provider(pi, api.Items), + IpcSubscribers.SetItemName.Provider(pi, api.Items), + + IpcSubscribers.GetState.Provider(pi, api.State), + IpcSubscribers.GetStateName.Provider(pi, api.State), + IpcSubscribers.ApplyState.Provider(pi, api.State), + IpcSubscribers.ApplyStateName.Provider(pi, api.State), + IpcSubscribers.RevertState.Provider(pi, api.State), + IpcSubscribers.RevertStateName.Provider(pi, api.State), + IpcSubscribers.UnlockState.Provider(pi, api.State), + IpcSubscribers.UnlockStateName.Provider(pi, api.State), + IpcSubscribers.UnlockAll.Provider(pi, api.State), + IpcSubscribers.RevertToAutomation.Provider(pi, api.State), + IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), + IpcSubscribers.StateChanged.Provider(pi, api.State), + IpcSubscribers.GPoseChanged.Provider(pi, api.State), + ]; + _initializedProvider.Invoke(); + } + + public void Dispose() + { + foreach (var provider in _providers) + provider.Dispose(); + _providers.Clear(); + _initializedProvider.Dispose(); + _disposedProvider.Invoke(); + _disposedProvider.Dispose(); + } +} diff --git a/Glamourer/Api/IpcSubscribers/Designs.cs b/Glamourer/Api/IpcSubscribers/Designs.cs new file mode 100644 index 0000000..8b31f6e --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/Designs.cs @@ -0,0 +1,52 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class GetDesignList(DalamudPluginInterface pi) + : FuncSubscriber>(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetDesignList)}"; + + /// + public new Dictionary Invoke() + => base.Invoke(); + + /// Create a provider. + public static FuncProvider> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, api.GetDesignList); +} + +/// +public sealed class ApplyDesign(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyDesign)}"; + + /// + public GlamourerApiEc Invoke(Guid designId, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) + => (GlamourerApiEc)Invoke(designId, objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesign(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class ApplyDesignName(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyDesignName)}"; + + /// + public GlamourerApiEc Invoke(Guid designId, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) + => (GlamourerApiEc)Invoke(designId, objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesignName(a, b, c, (ApplyFlag)d)); +} diff --git a/Glamourer/Api/IpcSubscribers/Items.cs b/Glamourer/Api/IpcSubscribers/Items.cs new file mode 100644 index 0000000..2c9139c --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/Items.cs @@ -0,0 +1,39 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Enums; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class SetItem(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(SetItem)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) + => (GlamourerApiEc)Invoke(objectIndex, (byte)slot, itemId, stain, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) + => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItem(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); +} + +/// +public sealed class SetItemName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(SetItemName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) + => (GlamourerApiEc)Invoke(objectName, (byte)slot, itemId, stain, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) + => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItemName(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); +} diff --git a/Glamourer/Api/IpcSubscribers/PluginState.cs b/Glamourer/Api/IpcSubscribers/PluginState.cs new file mode 100644 index 0000000..107c51a --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/PluginState.cs @@ -0,0 +1,51 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class ApiVersion(DalamudPluginInterface pi) + : FuncSubscriber<(int, int)>(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApiVersion)}"; + + /// + public new (int Major, int Minor) Invoke() + => base.Invoke(); + + /// Create a provider. + public static FuncProvider<(int, int)> Provider(DalamudPluginInterface pi, IGlamourerApiBase api) + => new(pi, Label, () => api.ApiVersion); +} + +/// Triggered when the Glamourer API is initialized and ready. +public static class Initialized +{ + /// The label. + public const string Label = $"Glamourer.{nameof(Initialized)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi) + => new(pi, Label); +} + +/// Triggered when the Glamourer API is fully disposed and unavailable. +public static class Disposed +{ + /// The label. + public const string Label = $"Glamourer.{nameof(Disposed)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi) + => new(pi, Label); +} diff --git a/Glamourer/Api/IpcSubscribers/State.cs b/Glamourer/Api/IpcSubscribers/State.cs new file mode 100644 index 0000000..e523c10 --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/State.cs @@ -0,0 +1,235 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Newtonsoft.Json.Linq; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class GetState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetState)}"; + + /// + public new (GlamourerApiEc, JObject?) Invoke(int objectIndex, uint key = 0) + { + var (ec, data) = base.Invoke(objectIndex, key); + return ((GlamourerApiEc)ec, data); + } + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => + { + var (ec, data) = api.GetState(a, b); + return ((int)ec, data); + }); +} + +/// +public sealed class GetStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetStateName)}"; + + /// + public new (GlamourerApiEc, JObject?) Invoke(string objectName, uint key = 0) + { + var (ec, data) = base.Invoke(objectName, key); + return ((GlamourerApiEc)ec, data); + } + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (i, k) => + { + var (ec, data) = api.GetStateName(i, k); + return ((int)ec, data); + }); +} + +/// +public sealed class ApplyState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyState)}"; + + /// + public GlamourerApiEc Invoke(JObject state, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(state, objectIndex, key, (ulong)flags); + + /// + public GlamourerApiEc Invoke(string base64State, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(base64State, objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyState(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class ApplyStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyStateName)}"; + + /// + public GlamourerApiEc Invoke(JObject state, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(state, objectName, key, (ulong)flags); + + /// + public GlamourerApiEc Invoke(string base64State, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(base64State, objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyStateName(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class RevertState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertState)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertState(a, b, (ApplyFlag)c)); +} + +/// +public sealed class RevertStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertStateName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertStateName(a, b, (ApplyFlag)c)); +} + +/// +public sealed class UnlockState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockState)}"; + + /// + public new GlamourerApiEc Invoke(int objectIndex, uint key = 0) + => (GlamourerApiEc)base.Invoke(objectIndex, key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => (int)api.UnlockState(a, b)); +} + +/// +public sealed class UnlockStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockStateName)}"; + + /// + public new GlamourerApiEc Invoke(string objectName, uint key = 0) + => (GlamourerApiEc)base.Invoke(objectName, key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => (int)api.UnlockStateName(a, b)); +} + +/// +public sealed class UnlockAll(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockAll)}"; + + /// + public new int Invoke(uint key) + => base.Invoke(key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, api.UnlockAll); +} + +/// +public sealed class RevertToAutomation(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertToAutomation)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertToAutomation(a, b, (ApplyFlag)c)); +} + +/// +public sealed class RevertToAutomationName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertToAutomationName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertToAutomationName(a, b, (ApplyFlag)c)); +} + +/// +public static class StateChanged +{ + /// The label. + public const string Label = $"Penumbra.{nameof(StateChanged)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (t => api.StateChanged += t, t => api.StateChanged -= t)); +} + +/// +public static class GPoseChanged +{ + /// The label. + public const string Label = $"Penumbra.{nameof(GPoseChanged)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (t => api.GPoseChanged += t, t => api.GPoseChanged -= t)); +} diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs new file mode 100644 index 0000000..baea51a --- /dev/null +++ b/Glamourer/Api/ItemsApi.cs @@ -0,0 +1,82 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.Services; +using Glamourer.State; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Api; + +public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager stateManager) : IGlamourerApiItems, IApiService +{ + public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + if (!ResolveItem(slot, itemId, out var item)) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, 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); + + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings); + ApiHelpers.Lock(state, key, flags); + return GlamourerApiEc.Success; + } + + public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + if (!ResolveItem(slot, itemId, 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 anyUnlocked = false; + foreach (var state in helpers.FindStates(objectName)) + { + anyFound = true; + if (!state.ModelData.IsHuman) + continue; + + anyHuman = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings); + ApiHelpers.Lock(state, key, flags); + } + + 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; + var slot = (EquipSlot)apiSlot; + if (id.Id == 0) + id = ItemManager.NothingId(slot); + + item = itemManager.Resolve(slot, id); + return item.Valid; + } +} diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs new file mode 100644 index 0000000..d540c7c --- /dev/null +++ b/Glamourer/Api/StateApi.cs @@ -0,0 +1,328 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Automation; +using Glamourer.Designs; +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 GPoseService _gPose; + + public StateApi(ApiHelpers helpers, + StateManager stateManager, + DesignConverter converter, + Configuration config, + AutoDesignApplier autoDesigns, + ObjectManager objects, + StateChanged stateChanged, + GPoseService gPose) + { + _helpers = helpers; + _stateManager = stateManager; + _converter = converter; + _config = config; + _autoDesigns = autoDesigns; + _objects = objects; + _stateChanged = stateChanged; + _gPose = gPose; + _stateChanged.Subscribe(OnStateChange, Events.StateChanged.Priority.GlamourerIpc); + _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); + } + + public void Dispose() + { + _stateChanged.Unsubscribe(OnStateChange); + _gPose.Unsubscribe(OnGPoseChange); + } + + public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key) + => Convert(_helpers.FindState(objectIndex), key); + + public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key) + => Convert(_helpers.FindStates(objectName).FirstOrDefault(), key); + + public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (Convert(applyState, flags, out var version) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + if (_helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + if (version < 3 && state.ModelData.ModelId != 0) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + ApplyDesign(state, design, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc ApplyStateName(object applyState, string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + if (Convert(applyState, flags, out var version) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + var anyHuman = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + if (version < 3 && state.ModelData.ModelId != 0) + continue; + + anyHuman = true; + ApplyDesign(state, design, key, flags); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + if (!anyHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + Revert(state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + Revert(state, key, flags); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc UnlockState(int objectIndex, uint key) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.Unlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc UnlockStateName(string objectName, uint key) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + foreach (var state in states) + { + any = true; + anyUnlocked |= state.Unlock(key); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public int UnlockAll(uint key) + => _stateManager.Values.Count(state => state.Unlock(key)); + + public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + RevertToAutomation(_objects[objectIndex], state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + var anyReverted = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + anyReverted |= RevertToAutomation(state, key, flags) is GlamourerApiEc.Success; + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyReverted) + ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public event Action? StateChanged; + 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); + _stateManager.ApplyDesign(state, design, settings); + ApiHelpers.Lock(state, key, flags); + } + + private void Revert(ActorState state, uint key, ApplyFlag flags) + { + 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; + } + + ApiHelpers.Lock(state, key, flags); + } + + private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags) + { + _objects.Update(); + if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) + return GlamourerApiEc.ActorNotFound; + + foreach (var actor in actors.Objects) + RevertToAutomation(actor, state, key, flags); + + return GlamourerApiEc.Success; + } + + 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); + _stateManager.ReapplyState(actor, state, source); + ApiHelpers.Lock(state, key, flags); + } + + private (GlamourerApiEc, JObject?) Convert(ActorState? state, uint key) + { + if (state == null) + return (GlamourerApiEc.ActorNotFound, null); + + if (!state.CanUnlock(key)) + return (GlamourerApiEc.InvalidKey, null); + + return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config))); + } + + private DesignBase? Convert(object? state, ApplyFlag flags, out byte version) + { + version = DesignConverter.Version; + return state switch + { + string s => _converter.FromBase64(s, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0, out version), + JObject j => _converter.FromJObject(j, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0), + _ => null, + }; + } + + private void OnGPoseChange(bool gPose) + => GPoseChanged?.Invoke(gPose); + + private void OnStateChange(StateChanged.Type _1, StateSource _2, ActorState _3, ActorData actors, object? _5) + { + if (StateChanged == null) + return; + + foreach (var actor in actors.Objects) + StateChanged.Invoke(actor.Address); + } +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 83b6cfd..14a3c0e 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -199,8 +199,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn continue; } - var settingsDict = tok["Settings"]?.ToObject>() ?? new Dictionary(); - var settings = new SortedList>(settingsDict.Count); + var settingsDict = tok["Settings"]?.ToObject>>() ?? []; + var settings = new Dictionary>(settingsDict.Count); foreach (var (key, value) in settingsDict) settings.Add(key, value); var priority = tok["Priority"]?.ToObject() ?? 0; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 3b67ac6..a7358b8 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -34,10 +34,10 @@ public class DesignConverter( } public string ShareBase64(Design design) - => ShareBase64(ShareJObject(design)); + => ToBase64(ShareJObject(design)); public string ShareBase64(DesignBase design) - => ShareBase64(ShareJObject(design)); + => ToBase64(ShareJObject(design)); public string ShareBase64(ActorState state, in ApplicationRules rules) => ShareBase64(state.ModelData, state.Materials, rules); @@ -45,7 +45,7 @@ public class DesignConverter( public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { var design = Convert(data, materials, rules); - return ShareBase64(ShareJObject(design)); + return ToBase64(ShareJObject(design)); } public DesignBase Convert(ActorState state, in ApplicationRules rules) @@ -61,6 +61,37 @@ public class DesignConverter( return design; } + public DesignBase? FromJObject(JObject? jObject, bool customize, bool equip) + { + if (jObject == null) + return null; + + try + { + var ret = jObject["Identifier"] != null + ? Design.LoadDesign(_customize, _items, _linkLoader, jObject) + : DesignBase.LoadDesignBase(_customize, _items, jObject); + + ret.SetApplyMeta(MetaIndex.Wetness, customize); + if (!customize) + ret.ApplyCustomize = 0; + + if (!equip) + { + ret.ApplyEquip = 0; + ret.ApplyCrest = 0; + ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + } + + return ret; + } + catch (Exception ex) + { + Glamourer.Log.Warning($"Failure to parse JObject to design:\n{ex}"); + return null; + } + } + public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version) { DesignBase ret; @@ -138,7 +169,7 @@ public class DesignConverter( return ret; } - private static string ShareBase64(JToken jObject) + public static string ToBase64(JToken jObject) { var json = jObject.ToString(Formatting.None); var compressed = json.Compress(Version); diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9ad0630..b368195 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -40,7 +40,7 @@ public class Glamourer : IDalamudPlugin _services.GetService(); // Initialize State Listener. _services.GetService(); // initialize ui. _services.GetService(); // initialize commands. - _services.GetService(); // initialize IPC. + _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); } catch diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 2519b84..3dce124 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,4 +1,5 @@ -using ImGuiNET; +using Glamourer.Gui.Tabs.DebugTab.IpcTester; +using ImGuiNET; using Microsoft.Extensions.DependencyInjection; 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 new file mode 100644 index 0000000..1a74778 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -0,0 +1,78 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService +{ + private Dictionary _designs = []; + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private Guid? _design; + private string _designText = string.Empty; + private GlamourerApiEc _lastError; + + public void Draw() + { + using var tree = ImRaii.TreeNode("Designs"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + IpcTesterHelpers.NameInput(ref _gameObjectName); + ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText, + ImGui.GetContentRegionAvail().X); + IpcTesterHelpers.DrawFlagInput(ref _flags); + + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + + IpcTesterHelpers.DrawIntro(GetDesignList.Label); + DrawDesignsPopup(); + if (ImGui.Button("Get##Designs")) + { + _designs = new GetDesignList(pluginInterface).Invoke(); + ImGui.OpenPopup("Designs"); + } + + IpcTesterHelpers.DrawIntro(ApplyDesign.Label); + if (ImGuiUtil.DrawDisabledButton("Apply##Idx", Vector2.Zero, string.Empty, !_design.HasValue)) + _lastError = new ApplyDesign(pluginInterface).Invoke(_design!.Value, _gameObjectIndex, _key, _flags); + + 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); + } + + private void DrawDesignsPopup() + { + ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500)); + using var p = ImRaii.Popup("Designs"); + if (!p) + return; + + using var table = ImRaii.Table("Designs", 2, ImGuiTableFlags.SizingFixedFit); + foreach (var (guid, name) in _designs) + { + ImGuiUtil.DrawTableColumn(name); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(guid.ToString()); + } + + if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + ImGui.CloseCurrentPopup(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs new file mode 100644 index 0000000..500fddd --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -0,0 +1,60 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using static Penumbra.GameData.Files.ShpkFile; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public static class IpcTesterHelpers +{ + public static void DrawFlagInput(ref ApplyFlag flags) + { + var value = (flags & ApplyFlag.Once) != 0; + if (ImGui.Checkbox("Apply Once", ref value)) + flags = value ? flags | ApplyFlag.Once : flags & ~ApplyFlag.Once; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Equipment) != 0; + if (ImGui.Checkbox("Apply Equipment", ref value)) + flags = value ? flags | ApplyFlag.Equipment : flags & ~ApplyFlag.Equipment; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Customization) != 0; + if (ImGui.Checkbox("Apply Customization", ref value)) + flags = value ? flags | ApplyFlag.Customization : flags & ~ApplyFlag.Customization; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Lock) != 0; + if (ImGui.Checkbox("Lock Actor", ref value)) + flags = value ? flags | ApplyFlag.Lock : flags & ~ApplyFlag.Lock; + } + + public static void IndexInput(ref int index) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + ImGui.InputInt("Game Object Index", ref index, 0, 0); + } + + public static void KeyInput(ref uint key) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + var keyI = (int)key; + if (ImGui.InputInt("Key", ref keyI, 0, 0)) + key = (uint)keyI; + } + + public static void NameInput(ref string name) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##gameObject", "Character Name...", ref name, 64); + } + + public static void DrawIntro(string intro) + { + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(intro); + ImGui.TableNextColumn(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs new file mode 100644 index 0000000..62203ac --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -0,0 +1,271 @@ +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using Glamourer.Api; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class IpcTesterPanel( + DalamudPluginInterface pluginInterface, + DesignIpcTester designs, + ItemsIpcTester items, + StateIpcTester state, + IFramework framework) : IGameDataDrawer +{ + public string Label + => "IPC Tester"; + + public bool Disabled + => false; + + private DateTime _lastUpdate; + private bool _subscribed = false; + + public void Draw() + { + try + { + _lastUpdate = framework.LastUpdateUTC.AddSeconds(1); + Subscribe(); + ImGui.TextUnformatted(ApiVersion.Label); + var (major, minor) = new ApiVersion(pluginInterface).Invoke(); + ImGui.SameLine(); + ImGui.TextUnformatted($"({major}.{minor:D4})"); + + designs.Draw(); + items.Draw(); + state.Draw(); + } + catch (Exception e) + { + Glamourer.Log.Error($"Error during IPC Tests:\n{e}"); + } + //ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); + //ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); + //ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); + //ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); + //DrawItemInput(); + //using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + //if (!table) + // return; + // + //ImGuiUtil.DrawTableColumn(); + //ImGui.TableNextColumn(); + //var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName); + //if (base64 != null) + // ImGuiUtil.CopyOnClickSelectable(base64); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); + //ImGui.TableNextColumn(); + //base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); + //if (base64 != null) + // ImGuiUtil.CopyOnClickSelectable(base64); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); + //ImGui.TableNextColumn(); + //var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + //if (base64Locked != null) + // ImGuiUtil.CopyOnClickSelectable(base64Locked); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##Name")) + // GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##Character")) + // GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##AllName")) + // GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##AllName")) + // GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##AllCharacter")) + // GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##AllCharacter")) + // GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##EquipName")) + // GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##EquipCharacter")) + // GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##CustomizeName")) + // GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##CustomizeCharacter")) + // GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) + // GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once)) + // GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) + // GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) + // .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) + // GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) + // .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply With Lock##CustomizeCharacter")) + // GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Unlock##CustomizeCharacter")) + // GlamourerIpc.UnlockSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Unlock All##CustomizeCharacter")) + // GlamourerIpc.UnlockAllSubscriber(_pluginInterface) + // .Invoke(1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##CustomizeCharacter")) + // GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); + //ImGui.TableNextColumn(); + //var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) + // .Invoke(); + //if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList")) + // ImGui.SetClipboardText(string.Join("\n", designList)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set##SetItem")) + // _setItemEc = (GlamourerApiEc)GlamourerIpc.SetItemSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set Once##SetItem")) + // _setItemOnceEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemOnceEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemOnceEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set##SetItemByActorName")) + // _setItemByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) + // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemByActorNameEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set Once##SetItemByActorName")) + // _setItemOnceByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface) + // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemOnceByActorNameEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString()); + //} + } + + private void Subscribe() + { + if (_subscribed) + return; + + Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); + state.GPoseChanged.Enable(); + state.StateChanged.Enable(); + framework.Update += CheckUnsubscribe; + _subscribed = true; + } + + private void CheckUnsubscribe(IFramework framework1) + { + if (_lastUpdate > framework.LastUpdateUTC) + return; + + Unsubscribe(); + framework.Update -= CheckUnsubscribe; + } + + private void Unsubscribe() + { + if (!_subscribed) + return; + + Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester."); + _subscribed = false; + state.GPoseChanged.Disable(); + state.StateChanged.Disable(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs new file mode 100644 index 0000000..ec75998 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -0,0 +1,66 @@ +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService +{ + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private CustomItemId _customItemId; + private StainId _stainId; + private EquipSlot _slot = EquipSlot.Head; + private GlamourerApiEc _lastError; + + public void Draw() + { + using var tree = ImRaii.TreeNode("Items"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + DrawItemInput(); + IpcTesterHelpers.NameInput(ref _gameObjectName); + IpcTesterHelpers.DrawFlagInput(ref _flags); + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + + IpcTesterHelpers.DrawIntro(SetItem.Label); + if (ImGui.Button("Set##Idx")) + _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + + IpcTesterHelpers.DrawIntro(SetItemName.Label); + if (ImGui.Button("Set##Name")) + _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + } + + private void DrawItemInput() + { + var tmp = _customItemId.Id; + var width = ImGui.GetContentRegionAvail().X / 2; + ImGui.SetNextItemWidth(width); + if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) + _customItemId = tmp; + EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot, width); + var value = (int)_stainId.Id; + ImGui.SetNextItemWidth(width); + if (ImGui.InputInt("Stain ID", ref value, 1, 3)) + { + value = Math.Clamp(value, 0, byte.MaxValue); + _stainId = (StainId)value; + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs new file mode 100644 index 0000000..5898177 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -0,0 +1,184 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using Glamourer.Designs; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Interop; +using Penumbra.String; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class StateIpcTester : IUiService, IDisposable +{ + private readonly DalamudPluginInterface _pluginInterface; + + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private GlamourerApiEc _lastError; + private JObject? _state; + private string? _stateString; + private string _base64State = string.Empty; + + public readonly EventSubscriber StateChanged; + private nint _lastStateChangeActor; + private ByteString _lastStateChangeName = ByteString.Empty; + private DateTime _lastStateChangeTime; + + public readonly EventSubscriber GPoseChanged; + private bool _lastGPoseChangeValue; + private DateTime _lastGPoseChangeTime; + + private int _numUnlocked; + + public StateIpcTester(DalamudPluginInterface pluginInterface) + { + _pluginInterface = pluginInterface; + StateChanged = Api.IpcSubscribers.StateChanged.Subscriber(_pluginInterface, OnStateChanged); + GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + } + + public void Dispose() + { + StateChanged.Dispose(); + GPoseChanged.Dispose(); + } + + public void Draw() + { + using var tree = ImRaii.TreeNode("State"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + IpcTesterHelpers.NameInput(ref _gameObjectName); + IpcTesterHelpers.DrawFlagInput(ref _flags); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##base64", "Base 64 State...", ref _base64State, 2000); + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + IpcTesterHelpers.DrawIntro("Last State Change"); + PrintName(); + IpcTesterHelpers.DrawIntro("Last GPose Change"); + ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}"); + + + IpcTesterHelpers.DrawIntro(GetState.Label); + DrawStatePopup(); + if (ImGui.Button("Get##Idx")) + { + (_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key); + _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; + ImGui.OpenPopup("State"); + } + + IpcTesterHelpers.DrawIntro(GetStateName.Label); + if (ImGui.Button("Get##Name")) + { + (_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key); + _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; + ImGui.OpenPopup("State"); + } + + 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")) + _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")) + _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertState.Label); + if (ImGui.Button("Revert##Idx")) + _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertStateName.Label); + if (ImGui.Button("Revert##Name")) + _lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(UnlockState.Label); + if (ImGui.Button("Unlock##Idx")) + _lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key); + + IpcTesterHelpers.DrawIntro(UnlockStateName.Label); + if (ImGui.Button("Unlock##Name")) + _lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key); + + IpcTesterHelpers.DrawIntro(UnlockAll.Label); + if (ImGui.Button("Unlock##All")) + _numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key); + ImGui.SameLine(); + ImGui.TextUnformatted($"Unlocked {_numUnlocked}"); + + IpcTesterHelpers.DrawIntro(RevertToAutomation.Label); + if (ImGui.Button("Revert##AutomationIdx")) + _lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label); + if (ImGui.Button("Revert##AutomationName")) + _lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + } + + private void DrawStatePopup() + { + ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500)); + using var p = ImRaii.Popup("State"); + if (!p) + return; + + if (ImGui.Button("Copy to Clipboard")) + ImGui.SetClipboardText(_stateString); + ImGui.SameLine(); + if (ImGui.Button("Copy as Base64") && _state != null) + ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(_stateString); + + if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + ImGui.CloseCurrentPopup(); + } + + private unsafe void PrintName() + { + ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); + } + + ImGui.SameLine(); + ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); + } + + private void OnStateChanged(nint actor) + { + _lastStateChangeActor = actor; + _lastStateChangeTime = DateTime.UtcNow; + _lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + } + + private void OnGPoseChange(bool value) + { + _lastGPoseChangeValue = value; + _lastGPoseChangeTime = DateTime.UtcNow; + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs deleted file mode 100644 index d96aefa..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ /dev/null @@ -1,244 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api; -using Glamourer.Interop; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Gui; -using Penumbra.GameData.Gui.Debug; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer -{ - public string Label - => "IPC Tester"; - - public bool Disabled - => false; - - private int _gameObjectIndex; - private CustomItemId _customItemId; - private StainId _stainId; - private EquipSlot _slot = EquipSlot.Head; - private string _gameObjectName = string.Empty; - private string _base64Apply = string.Empty; - private string _designIdentifier = string.Empty; - private GlamourerIpc.GlamourerErrorCode _setItemEc; - private GlamourerIpc.GlamourerErrorCode _setItemOnceEc; - private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc; - private GlamourerIpc.GlamourerErrorCode _setItemOnceByActorNameEc; - - public void Draw() - { - ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); - ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); - ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); - DrawItemInput(); - using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApiVersions); - var (major, minor) = GlamourerIpc.ApiVersionsSubscriber(_pluginInterface).Invoke(); - ImGuiUtil.DrawTableColumn($"({major}, {minor})"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomization); - ImGui.TableNextColumn(); - var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName); - if (base64 != null) - ImGuiUtil.CopyOnClickSelectable(base64); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); - ImGui.TableNextColumn(); - base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - if (base64 != null) - ImGuiUtil.CopyOnClickSelectable(base64); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); - ImGui.TableNextColumn(); - var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - if (base64Locked != null) - ImGuiUtil.CopyOnClickSelectable(base64Locked); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##Name")) - GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##Character")) - GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllName")) - GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##AllName")) - GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllCharacter")) - GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##AllCharacter")) - GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##EquipName")) - GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##EquipCharacter")) - GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##CustomizeName")) - GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##CustomizeCharacter")) - GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) - GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once)) - GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) - GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) - .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) - GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) - .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply With Lock##CustomizeCharacter")) - GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); - ImGui.TableNextColumn(); - if (ImGui.Button("Unlock##CustomizeCharacter")) - GlamourerIpc.UnlockSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); - ImGui.TableNextColumn(); - if (ImGui.Button("Unlock All##CustomizeCharacter")) - GlamourerIpc.UnlockAllSubscriber(_pluginInterface) - .Invoke(1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##CustomizeCharacter")) - GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); - ImGui.TableNextColumn(); - var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) - .Invoke(); - if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList")) - ImGui.SetClipboardText(string.Join("\n", designList)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); - ImGui.TableNextColumn(); - if (ImGui.Button("Set##SetItem")) - _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Set Once##SetItem")) - _setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemOnceEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); - ImGui.TableNextColumn(); - if (ImGui.Button("Set##SetItemByActorName")) - _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName); - ImGui.TableNextColumn(); - if (ImGui.Button("Set Once##SetItemByActorName")) - _setItemOnceByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemOnceByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString()); - } - } - - private void DrawItemInput() - { - var tmp = _customItemId.Id; - if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) - _customItemId = tmp; - var width = ImGui.GetContentRegionAvail().X; - EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); - var value = (int)_stainId.Id; - ImGui.SameLine(); - width -= ImGui.GetContentRegionAvail().X; - ImGui.SetNextItemWidth(width); - if (ImGui.InputInt("Stain ID", ref value, 1, 3)) - { - value = Math.Clamp(value, 0, byte.MaxValue); - _stainId = (StainId)value; - } - } -} diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 0618d14..164fe78 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -11,24 +11,13 @@ using OtterGui.Raii; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab +public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) { - private readonly PenumbraService _penumbra; - private readonly DesignFileSystemSelector _selector; - private readonly DesignManager _manager; - private readonly ModCombo _modCombo; - - public ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) - { - _penumbra = penumbra; - _selector = selector; - _manager = manager; - _modCombo = new ModCombo(penumbra, Glamourer.Log); - } + private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); public void Draw() { - using var h = ImRaii.CollapsingHeader("Mod Associations"); + using var h = ImRaii.CollapsingHeader("Mod Associations"); 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" @@ -43,25 +32,25 @@ public class ModAssociationsTab private void DrawApplyAllButton() { - var current = _penumbra.CurrentCollection; - if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {current}##applyAll", - new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, current is "")) + var (id, name) = penumbra.CurrentCollection; + if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", + new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) ApplyAll(); } public void DrawApplyButton() { - var current = _penumbra.CurrentCollection; + var (id, name) = penumbra.CurrentCollection; if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero, - $"Try to apply all associated mod settings to Penumbras current collection {current}", - _selector.Selected!.AssociatedMods.Count == 0 || current is "")) + $"Try to apply all associated mod settings to Penumbras current collection {name}", + selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty)) ApplyAll(); } public void ApplyAll() { - foreach (var (mod, settings) in _selector.Selected!.AssociatedMods) - _penumbra.SetMod(mod, settings); + foreach (var (mod, settings) in selector.Selected!.AssociatedMods) + penumbra.SetMod(mod, settings); } private void DrawTable() @@ -79,9 +68,9 @@ public class ModAssociationsTab ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X); ImGui.TableHeadersRow(); - Mod? removedMod = null; + Mod? removedMod = null; (Mod mod, ModSettings settings)? updatedMod = null; - foreach (var ((mod, settings), idx) in _selector.Selected!.AssociatedMods.WithIndex()) + foreach (var ((mod, settings), idx) in selector.Selected!.AssociatedMods.WithIndex()) { using var id = ImRaii.PushId(idx); DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp); @@ -94,10 +83,10 @@ public class ModAssociationsTab DrawNewModRow(); if (removedMod.HasValue) - _manager.RemoveMod(_selector.Selected!, removedMod.Value); - + manager.RemoveMod(selector.Selected!, removedMod.Value); + if (updatedMod.HasValue) - _manager.UpdateMod(_selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); + manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); } private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod) @@ -112,15 +101,15 @@ public class ModAssociationsTab ImGui.TableNextColumn(); ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Update the settings of this mod association", false, true); - + if (ImGui.IsItemHovered()) { - var newSettings = _penumbra.GetModSettings(mod); + var newSettings = penumbra.GetModSettings(mod); if (ImGui.IsItemClicked()) updatedMod = (mod, newSettings); - + using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); - using var tt = ImRaii.Tooltip(); + using var tt = ImRaii.Tooltip(); ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); @@ -143,7 +132,7 @@ public class ModAssociationsTab ModCombo.DrawSettingsRight(newSettings); } } - + ImGui.TableNextColumn(); var selected = ImGui.Selectable($"{mod.Name}##name"); var hovered = ImGui.IsItemHovered(); @@ -151,7 +140,7 @@ public class ModAssociationsTab selected |= ImGui.Selectable($"{mod.DirectoryName}##directory"); hovered |= ImGui.IsItemHovered(); if (selected) - _penumbra.OpenModPage(mod); + penumbra.OpenModPage(mod); if (hovered) ImGui.SetTooltip("Click to open mod page in Penumbra."); ImGui.TableNextColumn(); @@ -164,9 +153,9 @@ public class ModAssociationsTab ImGuiUtil.RightAlign(settings.Priority.ToString()); ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, - !_penumbra.Available)) + !penumbra.Available)) { - var text = _penumbra.SetMod(mod, settings); + var text = penumbra.SetMod(mod, settings); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } @@ -202,13 +191,13 @@ public class ModAssociationsTab ImGui.TableNextColumn(); var tt = currentName.IsNullOrEmpty() ? "Please select a mod first." - : _selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) + : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) ? "The design already contains an association with the selected mod." : string.Empty; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0, true)) - _manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); + manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); ImGui.TableNextColumn(); _modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty, ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()); diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs new file mode 100644 index 0000000..98ba870 --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -0,0 +1,36 @@ +using Dalamud.Interface; +using Glamourer.Interop.Penumbra; +using ImGuiNET; +using OtterGui; +using OtterGui.Log; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.SettingsTab; + +public sealed class CollectionCombo(Configuration config, PenumbraService penumbra, Logger log) + : FilterComboCache<(Guid Id, string IdShort, string Name)>( + () => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(), + MouseWheelType.Control, log), IUiService +{ + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var (_, idShort, name) = Items[globalIdx]; + if (config.Ephemeral.IncognitoMode) + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + return ImGui.Selectable(idShort); + } + + var ret = ImGui.Selectable(name, selected); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + ImGuiUtil.RightAlign($"({idShort})"); + } + + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 2ddabda..d976d28 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,13 +1,12 @@ using Dalamud.Interface; -using Dalamud.Interface.Components; -using Dalamud.Interface.Style; -using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Actors; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -15,13 +14,14 @@ public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, Configuration config, ObjectManager objects, - ActorManager actors) : IService + ActorManager actors, + PenumbraService penumbra, + CollectionCombo combo) : IService { private string _newIdentifier = string.Empty; private ActorIdentifier[] _identifiers = []; private int _dragDropIndex = -1; private Exception? _exception; - private string _collection = string.Empty; public void Draw() { @@ -32,58 +32,113 @@ public class CollectionOverrideDrawer( if (!header) return; - using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg); + using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg); if (!table) return; var buttonSize = new Vector2(ImGui.GetFrameHeight()); ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); - ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.6f); + ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.35f); ImGui.TableSetupColumn("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f); + ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.25f); for (var i = 0; i < collectionOverrides.Overrides.Count; ++i) + DrawCollectionRow(ref i, buttonSize); + + DrawNewOverride(buttonSize); + } + + private void DrawCollectionRow(ref int idx, Vector2 buttonSize) + { + using var id = ImRaii.PushId(idx); + var (exists, actor, collection, name) = collectionOverrides.Fetch(idx); + + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true)) + collectionOverrides.DeleteOverride(idx--); + + ImGui.TableNextColumn(); + DrawActorIdentifier(idx, actor); + + ImGui.TableNextColumn(); + if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())) { - var (identifier, collection) = collectionOverrides.Overrides[i]; - using var id = ImRaii.PushId(i); - ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true)) - collectionOverrides.DeleteOverride(i--); + var (guid, _, newName) = combo.CurrentSelection; + collectionOverrides.ChangeOverride(idx, guid, newName); + } - ImGui.TableNextColumn(); - ImGui.Selectable(config.Ephemeral.IncognitoMode ? identifier.Incognito(null) : identifier.ToString()); - - using (var target = ImRaii.DragDropTarget()) - { - if (target.Success && ImGuiUtil.IsDropping("DraggingOverride")) - { - collectionOverrides.MoveOverride(_dragDropIndex, i); - _dragDropIndex = -1; - } - } - - using (var source = ImRaii.DragDropSource()) - { - if (source) - { - ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); - ImGui.TextUnformatted($"Reordering Override #{i + 1}..."); - _dragDropIndex = i; - } - } - - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputText("##input", ref collection, 64) && collection.Length > 0) - collectionOverrides.ChangeOverride(i, collection); + if (ImGui.IsItemHovered()) + { + using var tt = ImRaii.Tooltip(); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted($" {collection}"); } + ImGui.TableNextColumn(); + DrawCollectionName(exists, collection, name); + } + + private void DrawCollectionName(bool exists, Guid collection, string name) + { + if (!exists) + { + ImGui.TextUnformatted(""); + if (!ImGui.IsItemHovered()) + return; + + using var tt1 = ImRaii.Tooltip(); + ImGui.TextUnformatted($"The design {name} with the GUID"); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($" {collection}"); + } + + ImGui.TextUnformatted("does not exist in Penumbra."); + return; + } + + ImGui.TextUnformatted(config.Ephemeral.IncognitoMode ? collection.ToString()[..8] : name); + if (!ImGui.IsItemHovered()) + return; + + using var tt2 = ImRaii.Tooltip(); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(collection.ToString()); + } + + private void DrawActorIdentifier(int idx, ActorIdentifier actor) + { + ImGui.Selectable(config.Ephemeral.IncognitoMode ? actor.Incognito(null) : actor.ToString()); + using (var target = ImRaii.DragDropTarget()) + { + if (target.Success && ImGuiUtil.IsDropping("DraggingOverride")) + { + collectionOverrides.MoveOverride(_dragDropIndex, idx); + _dragDropIndex = -1; + } + } + + using (var source = ImRaii.DragDropSource()) + { + if (source) + { + ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); + ImGui.TextUnformatted($"Reordering Override #{idx + 1}..."); + _dragDropIndex = idx; + } + } + } + + private void DrawNewOverride(Vector2 buttonSize) + { + var (currentId, currentName) = penumbra.CurrentCollection; ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.", - !objects.Player.Valid, true)) - collectionOverrides.AddOverride([objects.PlayerData.Identifier], _collection.Length > 0 ? _collection : "TempCollection"); + !objects.Player.Valid && currentId != Guid.Empty, true)) + collectionOverrides.AddOverride([objects.PlayerData.Identifier], currentId, currentName); ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - buttonSize.X * 2); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80)) try { @@ -91,7 +146,7 @@ public class CollectionOverrideDrawer( } catch (ActorIdentifierFactory.IdentifierParseError e) { - _exception = e; + _exception = e; _identifiers = []; } @@ -101,9 +156,9 @@ public class CollectionOverrideDrawer( ? "Please enter an identifier string first." : $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}"; - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is 'T', true)) - collectionOverrides.AddOverride(_identifiers, _collection.Length > 0 ? _collection : "TempCollection"); + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is not 'A', true)) + collectionOverrides.AddOverride(_identifiers, currentId, currentName); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); using (ImRaii.PushFont(UiBuilder.IconFont)) @@ -114,9 +169,5 @@ public class CollectionOverrideDrawer( if (ImGui.IsItemHovered()) ActorIdentifierFactory.WriteUserStringTooltip(false); - - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputTextWithHint("##collection", "Enter Collection...", ref _collection, 80); } } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 2b72fff..fcdc7b7 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -22,16 +22,13 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O return; } - var collections = new HashSet(); + var collections = new HashSet(); foreach (var actor in data.Objects) { - var (collection, overridden) = overrides.GetCollection(actor, state.Identifier); - if (collection.Length == 0) - { - Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}."); + var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); + if (collection == Guid.Empty) continue; - } if (!collections.Add(collection)) continue; @@ -48,11 +45,11 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O } } - public (List Messages, int Applied, string Collection, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) + public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) { - var (collection, overridden) = overrides.GetCollection(actor); - if (collection.Length <= 0) - return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false); + var (collection, name, overridden) = overrides.GetCollection(actor); + if (collection == Guid.Empty) + return ([$"{actor.Utf8Name} uses no mods."], 0, Guid.Empty, string.Empty, false); var messages = new List(); var appliedMods = 0; @@ -65,6 +62,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O ++appliedMods; } - return (messages, appliedMods, collection, overridden); + return (messages, appliedMods, collection, name, overridden); } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index c018113..11f7fd9 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -67,7 +67,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } } - private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) + private void OnModSettingChange(ModSettingChange type, Guid collectionId, string mod, bool inherited) { if (type is ModSettingChange.TemporaryMod) { @@ -79,8 +79,8 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) continue; - var collection = _penumbra.GetActorCollection(actors.Objects[0]); - if (collection != name) + var collection = _penumbra.GetActorCollection(actors.Objects[0], out _); + if (collection != collectionId) continue; _actions.Enqueue((state, () => @@ -96,7 +96,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService { // Only update once per frame. var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName != name) + if (playerName != collectionId) return; var currentFrame = _framework.LastUpdateUTC; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index c6617e7..6d4c677 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin; using Glamourer.Events; using OtterGui.Classes; -using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.GameData.Interop; @@ -10,8 +9,6 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -using CurrentSettings = ValueTuple>, bool)?>; - public readonly record struct Mod(string Name, string DirectoryName) : IComparable { public int CompareTo(Mod other) @@ -24,10 +21,10 @@ public readonly record struct Mod(string Name, string DirectoryName) : IComparab } } -public readonly record struct ModSettings(IDictionary> Settings, int Priority, bool Enabled) +public readonly record struct ModSettings(Dictionary> Settings, int Priority, bool Enabled) { public ModSettings() - : this(new Dictionary>(), 0, false) + : this(new Dictionary>(), 0, false) { } public static ModSettings Empty @@ -36,30 +33,33 @@ public readonly record struct ModSettings(IDictionary> Set public unsafe class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 4; - public const int RequiredPenumbraFeatureVersion = 15; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 0; - private readonly DalamudPluginInterface _pluginInterface; - private readonly EventSubscriber _tooltipSubscriber; - private readonly EventSubscriber _clickSubscriber; - private readonly EventSubscriber _creatingCharacterBase; - private readonly EventSubscriber _createdCharacterBase; - private readonly EventSubscriber _modSettingChanged; - private ActionSubscriber _redrawSubscriber; - private FuncSubscriber _drawObjectInfo; - private FuncSubscriber _cutsceneParent; - private FuncSubscriber _objectCollection; - private FuncSubscriber> _getMods; - private FuncSubscriber _currentCollection; - private FuncSubscriber _getCurrentSettings; - private FuncSubscriber _setMod; - private FuncSubscriber _setModPriority; - private FuncSubscriber _setModSetting; - private FuncSubscriber, PenumbraApiEc> _setModSettings; - private FuncSubscriber _openModPage; + private readonly DalamudPluginInterface _pluginInterface; + private readonly EventSubscriber _tooltipSubscriber; + private readonly EventSubscriber _clickSubscriber; + private readonly EventSubscriber _creatingCharacterBase; + private readonly EventSubscriber _createdCharacterBase; + private readonly EventSubscriber _modSettingChanged; - private readonly EventSubscriber _initializedEvent; - private readonly EventSubscriber _disposedEvent; + 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.GetCurrentModSettings? _getCurrentSettings; + 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.OpenMainWindow? _openModPage; + + private readonly IDisposable _initializedEvent; + private readonly IDisposable _disposedEvent; private readonly PenumbraReloaded _penumbraReloaded; @@ -69,13 +69,13 @@ public unsafe class PenumbraService : IDisposable { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; - _initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach); - _disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach); - _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi); - _clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi); - _createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi); - _creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi); - _modSettingChanged = Ipc.ModSettingChanged.Subscriber(pi); + _initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach); + _disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach); + _tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi); + _clickSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemClicked.Subscriber(pi); + _createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi); + _creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi); + _modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi); Reattach(); } @@ -92,24 +92,27 @@ public unsafe class PenumbraService : IDisposable } - public event Action CreatingCharacterBase + public event Action CreatingCharacterBase { add => _creatingCharacterBase.Event += value; remove => _creatingCharacterBase.Event -= value; } - public event Action CreatedCharacterBase + public event Action CreatedCharacterBase { add => _createdCharacterBase.Event += value; remove => _createdCharacterBase.Event -= value; } - public event Action ModSettingChanged + public event Action ModSettingChanged { add => _modSettingChanged.Event += value; remove => _modSettingChanged.Event -= value; } + public Dictionary GetCollections() + => Available ? _collections!.Invoke() : []; + public ModSettings GetModSettings(in Mod mod) { if (!Available) @@ -117,8 +120,8 @@ public unsafe class PenumbraService : IDisposable try { - var collection = _currentCollection.Invoke(ApiCollectionType.Current); - var (ec, tuple) = _getCurrentSettings.Invoke(collection, mod.DirectoryName, string.Empty, false); + var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -131,6 +134,18 @@ public unsafe class PenumbraService : IDisposable } } + public (Guid Id, string Name)? CollectionByIdentifier(string identifier) + { + if (!Available) + return null; + + var ret = _collectionByIdentifier!.Invoke(identifier); + if (ret.Count == 0) + return null; + + return ret[0]; + } + public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() { if (!Available) @@ -138,10 +153,10 @@ public unsafe class PenumbraService : IDisposable try { - var allMods = _getMods.Invoke(); - var collection = _currentCollection.Invoke(ApiCollectionType.Current); + var allMods = _getMods!.Invoke(); + var collection = _currentCollection!.Invoke(ApiCollectionType.Current); return allMods - .Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, false))) + .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 @@ -162,19 +177,22 @@ public unsafe class PenumbraService : IDisposable public void OpenModPage(Mod mod) { - if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) + if (!Available) + return; + + if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName) == PenumbraApiEc.ModMissing) Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); } - public string CurrentCollection - => Available ? _currentCollection.Invoke(ApiCollectionType.Current) : ""; + public (Guid Id, string Name) CurrentCollection + => Available ? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value : (Guid.Empty, ""); /// /// 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, string? collection = null) + public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null) { if (!Available) return "Penumbra is not available."; @@ -182,8 +200,8 @@ public unsafe class PenumbraService : IDisposable var sb = new StringBuilder(); try { - collection ??= _currentCollection.Invoke(ApiCollectionType.Current); - var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); + var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; + var ec = _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); switch (ec) { case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; @@ -193,14 +211,14 @@ public unsafe class PenumbraService : IDisposable if (!settings.Enabled) return string.Empty; - ec = _setModPriority.Invoke(collection, mod.DirectoryName, mod.Name, settings.Priority); + ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority); 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, mod.Name, setting, list[0]) - : _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList)list); + ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0]) + : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); switch (ec) { case PenumbraApiEc.OptionGroupMissing: @@ -227,55 +245,54 @@ public unsafe class PenumbraService : IDisposable } /// Obtain the name of the collection currently assigned to the player. - public string GetCurrentPlayerCollection() + public Guid GetCurrentPlayerCollection() { if (!Available) - return string.Empty; + return Guid.Empty; - var (valid, _, name) = _objectCollection.Invoke(0); - return valid ? name : string.Empty; + var (valid, _, (id, _)) = _objectCollection!.Invoke(0); + return valid ? id : Guid.Empty; } /// Obtain the name of the collection currently assigned to the given actor. - public string GetActorCollection(Actor actor) + public Guid GetActorCollection(Actor actor, out string name) { if (!Available) - return string.Empty; + { + name = string.Empty; + return Guid.Empty; + } - var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index); - return valid ? name : string.Empty; + (var valid, _, (var id, name)) = _objectCollection!.Invoke(actor.Index.Index); + return valid ? id : Guid.Empty; } /// Obtain the game object corresponding to a draw object. public Actor GameObjectFromDrawObject(Model drawObject) - => Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; + => Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : 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)(Available ? _cutsceneParent!.Invoke(idx) : -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) { - if (!actor || !Available) + if (!actor) return; - try - { - _redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings); - } - catch (Exception e) - { - Glamourer.Log.Debug($"Failure redrawing object:\n{e}"); - } + RedrawObject(actor.Index, settings); } /// Try to redraw the given actor. public void RedrawObject(ObjectIndex index, RedrawType settings) { + if (!Available) + return; + try { - _redrawSubscriber.Invoke(index.Index, settings); + _redraw!.Invoke(index.Index, settings); } catch (Exception e) { @@ -290,7 +307,7 @@ public unsafe class PenumbraService : IDisposable { Unattach(); - var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke(); + var (breaking, feature) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke(); if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) throw new Exception( $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); @@ -300,24 +317,27 @@ public unsafe class PenumbraService : IDisposable _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); _modSettingChanged.Enable(); - _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface); - _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface); - _redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface); - _objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface); - _getMods = Ipc.GetMods.Subscriber(_pluginInterface); - _currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface); - _getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface); - _setMod = Ipc.TrySetMod.Subscriber(_pluginInterface); - _setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface); - _setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface); - _setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface); - _openModPage = Ipc.OpenMainWindow.Subscriber(_pluginInterface); - Available = true; + _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); + _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); + Available = true; _penumbraReloaded.Invoke(); Glamourer.Log.Debug("Glamourer attached to Penumbra."); } catch (Exception e) { + Unattach(); Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}"); } } @@ -332,7 +352,21 @@ public unsafe class PenumbraService : IDisposable _modSettingChanged.Disable(); if (Available) { - Available = false; + _collectionByIdentifier = null; + _collections = null; + _redraw = null; + _drawObjectInfo = null; + _cutsceneParent = null; + _objectCollection = null; + _getMods = null; + _currentCollection = null; + _getCurrentSettings = null; + _setMod = null; + _setModPriority = null; + _setModSetting = null; + _setModSettings = null; + _openModPage = null; + Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } } diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index a7c4364..691118f 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,7 +1,9 @@ +using Dalamud.Interface.Internal.Notifications; using Glamourer.Interop.Penumbra; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; +using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; @@ -11,7 +13,7 @@ namespace Glamourer.Services; public sealed class CollectionOverrideService : IService, ISavable { - public const int Version = 1; + public const int Version = 2; private readonly SaveService _saveService; private readonly ActorManager _actors; private readonly PenumbraService _penumbra; @@ -24,48 +26,71 @@ public sealed class CollectionOverrideService : IService, ISavable Load(); } - public unsafe (string Collection, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default) + public unsafe (Guid CollectionId, string Display, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default) { if (!identifier.IsValid) identifier = _actors.FromObject(actor.AsObject, out _, true, true, true); return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret) - ? (ret.Collection, true) - : (_penumbra.GetActorCollection(actor), false); + ? (ret.CollectionId, ret.DisplayName, true) + : (_penumbra.GetActorCollection(actor, out var name), name, false); } - private readonly List<(ActorIdentifier Actor, string Collection)> _overrides = []; + private readonly List<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> _overrides = []; - public IReadOnlyList<(ActorIdentifier Actor, string Collection)> Overrides + public IReadOnlyList<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> Overrides => _overrides; public string ToFilename(FilenameService fileNames) => fileNames.CollectionOverrideFile; - public void AddOverride(IEnumerable identifiers, string collection) + public void AddOverride(IEnumerable identifiers, Guid collectionId, string displayName) { - if (collection.Length == 0) + if (collectionId == Guid.Empty) return; foreach (var id in identifiers.Where(i => i.IsValid)) { - _overrides.Add((id, collection)); - Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}."); + _overrides.Add((id, collectionId, displayName)); + Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collectionId}."); _saveService.QueueSave(this); } } - public void ChangeOverride(int idx, string newCollection) + public (bool Exists, ActorIdentifier Identifier, Guid CollectionId, string DisplayName) Fetch(int idx) { - if (idx < 0 || idx >= _overrides.Count || newCollection.Length == 0) + var (identifier, id, name) = _overrides[idx]; + var collection = _penumbra.CollectionByIdentifier(id.ToString()); + if (collection == null) + return (false, identifier, id, name); + + if (collection.Value.Name == name) + return (true, identifier, id, name); + + _overrides[idx] = (identifier, id, collection.Value.Name); + Glamourer.Log.Debug($"Updated display name of collection override {idx + 1} ({id})."); + _saveService.QueueSave(this); + return (true, identifier, id, collection.Value.Name); + } + + public void ChangeOverride(int idx, Guid newCollectionId, string newDisplayName) + { + if (idx < 0 || idx >= _overrides.Count) + return; + + if (newCollectionId == Guid.Empty || newDisplayName.Length == 0) return; var current = _overrides[idx]; - if (current.Collection == newCollection) + if (current.CollectionId == newCollectionId) return; - _overrides[idx] = current with { Collection = newCollection }; - Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}."); + _overrides[idx] = current with + { + CollectionId = newCollectionId, + DisplayName = newDisplayName, + }; + Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.CollectionId} to {newCollectionId}."); _saveService.QueueSave(this); } @@ -102,6 +127,7 @@ public sealed class CollectionOverrideService : IService, ISavable switch (version) { case 1: + case 2: if (jObj["Overrides"] is not JArray array) { Glamourer.Log.Error($"Invalid format of collection override file, ignored."); @@ -110,17 +136,50 @@ public sealed class CollectionOverrideService : IService, ISavable foreach (var token in array.OfType()) { - var collection = token["Collection"]?.ToObject() ?? string.Empty; - var identifier = _actors.FromJson(token); + var collectionIdentifier = token["Collection"]?.ToObject() ?? string.Empty; + var identifier = _actors.FromJson(token); + var displayName = token["DisplayName"]?.ToObject() ?? collectionIdentifier; if (!identifier.IsValid) - Glamourer.Log.Warning($"Invalid identifier for collection override with collection [{collection}], skipped."); - else if (collection.Length == 0) - Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped."); - else - _overrides.Add((identifier, collection)); + { + Glamourer.Log.Warning( + $"Invalid identifier for collection override with collection [{token["Collection"]}], skipped."); + continue; + } + + if (!Guid.TryParse(collectionIdentifier, out var collectionId)) + { + if (collectionIdentifier.Length == 0) + { + Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped."); + continue; + } + + if (version >= 2) + { + Glamourer.Log.Warning( + $"Invalid collection override {collectionIdentifier} for identifier {identifier.Incognito(null)}, skipped."); + continue; + } + + var collection = _penumbra.CollectionByIdentifier(collectionIdentifier); + if (collection == null) + { + Glamourer.Messager.AddMessage(new Notification( + $"The overridden collection for identifier {identifier.Incognito(null)} with name {collectionIdentifier} could not be found by Penumbra for migration.", + NotificationType.Warning)); + continue; + } + + Glamourer.Log.Information($"Migrated collection {collectionIdentifier} to {collection.Value.Id}."); + collectionId = collection.Value.Id; + displayName = collection.Value.Name; + } + + _overrides.Add((identifier, collectionId, displayName)); } break; + default: Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored."); return; @@ -147,10 +206,11 @@ public sealed class CollectionOverrideService : IService, ISavable JArray SerializeOverrides() { var jArray = new JArray(); - foreach (var (actor, collection) in _overrides) + foreach (var (actor, collection, displayName) in _overrides) { var obj = actor.ToJson(); - obj["Collection"] = collection; + obj["Collection"] = collection; + obj["DisplayName"] = displayName; jArray.Add(obj); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e2edd2d..0616392 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -10,6 +10,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -18,7 +19,7 @@ using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Services; -public class CommandService : IDisposable +public class CommandService : IDisposable, IApiService { private const string RandomString = "random"; private const string MainCommandString = "/glamourer"; @@ -118,7 +119,7 @@ public class CommandService : IDisposable "apply" => Apply(argument), "reapply" => ReapplyState(argument), "revert" => Revert(argument), - "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), + "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), "automation" => SetAutomation(argument), "copy" => CopyState(argument), @@ -534,14 +535,14 @@ public class CommandService : IDisposable if (!applyMods || design is not Design d) return; - var (messages, appliedMods, collection, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); + var (messages, appliedMods, collection, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); if (appliedMods > 0) Glamourer.Messager.Chat.Print( - $"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); + $"Applied {appliedMods} mod settings to {name}{(overridden ? " (overridden by settings)" : string.Empty)}."); } private bool Delete(string argument) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 1fc7077..8f52815 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -65,20 +65,26 @@ public class ItemManager if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) + if (!itemId.IsItem) { - item = EquipItem.FromId(itemId); - item = slot is EquipSlot.MainHand or EquipSlot.OffHand - ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) + var item = EquipItem.FromId(itemId); + item = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) : Identify(slot, item.PrimaryId, item.Variant); return item; } + else if (!ItemData.TryGetValue(itemId.Item, slot, out var item)) + { + return EquipItem.FromId(itemId); + } + else + { + if (item.Type.ToSlot() != slot) + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, + 0, 0, 0, 0); - if (item.Type.ToSlot() != slot) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, - 0, 0, 0, 0); - - return item; + return item; + } } public EquipItem Resolve(FullEquipType type, ItemId itemId) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index d5f92a9..f06e014 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin; using Glamourer.Api; +using Glamourer.Api.Api; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; @@ -43,12 +44,12 @@ public static class StaticServiceManager .AddData() .AddDesigns() .AddState() - .AddUi() - .AddApi(); + .AddUi(); DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); services.AddIServices(typeof(ImRaii).Assembly); + services.AddSingleton(p => p.GetRequiredService()); services.CreateProvider(); return services; } @@ -164,8 +165,4 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton(); - - private static ServiceManager AddApi(this ServiceManager services) - => services.AddSingleton() - .AddSingleton(); } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 7a30108..73c8f0d 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -106,7 +106,7 @@ public class StateListener : IDisposable /// Weapons and meta flags are updated independently. /// We also need to apply fixed designs here. /// - private unsafe void OnCreatingCharacterBase(nint actorPtr, string _, nint modelPtr, nint customizePtr, nint equipDataPtr) + private unsafe void OnCreatingCharacterBase(nint actorPtr, Guid _, nint modelPtr, nint customizePtr, nint equipDataPtr) { var actor = (Actor)actorPtr; if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -725,7 +725,7 @@ public class StateListener : IDisposable _changeCustomizeService.Unsubscribe(OnCustomizeChanged); } - private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) + private void OnCreatedCharacterBase(nint gameObject, Guid _, nint drawObject) { if (_condition[ConditionFlag.CreatingCharacter]) return; From 21aa3e8efc73e9843aa503893c1a99d5caf36573 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:30:39 +0200 Subject: [PATCH 13/45] Extract API to own project. --- Glamourer.Api | 1 + Glamourer.sln | 6 + Glamourer/Api/Api/IGlamourerApi.cs | 8 - Glamourer/Api/Api/IGlamourerApiBase.cs | 6 - Glamourer/Api/Api/IGlamourerApiDesigns.cs | 12 - Glamourer/Api/Api/IGlamourerApiItems.cs | 9 - Glamourer/Api/Api/IGlamourerApiState.cs | 28 --- Glamourer/Api/Enums/ApplyFlag.cs | 34 --- Glamourer/Api/Enums/GlamourerApiEc.cs | 13 - Glamourer/Api/GlamourerApi.cs | 12 +- Glamourer/Api/IpcProviders.cs | 2 +- Glamourer/Api/IpcSubscribers/Designs.cs | 52 ---- Glamourer/Api/IpcSubscribers/Items.cs | 39 --- Glamourer/Api/IpcSubscribers/PluginState.cs | 51 ---- Glamourer/Api/IpcSubscribers/State.cs | 235 ------------------ Glamourer/Glamourer.csproj | 1 + .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 22 +- .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 2 +- 18 files changed, 28 insertions(+), 505 deletions(-) create mode 160000 Glamourer.Api delete mode 100644 Glamourer/Api/Api/IGlamourerApi.cs delete mode 100644 Glamourer/Api/Api/IGlamourerApiBase.cs delete mode 100644 Glamourer/Api/Api/IGlamourerApiDesigns.cs delete mode 100644 Glamourer/Api/Api/IGlamourerApiItems.cs delete mode 100644 Glamourer/Api/Api/IGlamourerApiState.cs delete mode 100644 Glamourer/Api/Enums/ApplyFlag.cs delete mode 100644 Glamourer/Api/Enums/GlamourerApiEc.cs delete mode 100644 Glamourer/Api/IpcSubscribers/Designs.cs delete mode 100644 Glamourer/Api/IpcSubscribers/Items.cs delete mode 100644 Glamourer/Api/IpcSubscribers/PluginState.cs delete mode 100644 Glamourer/Api/IpcSubscribers/State.cs diff --git a/Glamourer.Api b/Glamourer.Api new file mode 160000 index 0000000..0c8578c --- /dev/null +++ b/Glamourer.Api @@ -0,0 +1 @@ +Subproject commit 0c8578cfa12bf0591ed204fd89b30b66719f678f diff --git a/Glamourer.sln b/Glamourer.sln index 9acdd6c..254f8e4 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.Api", "Glamourer.Api\Glamourer.Api.csproj", "{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Glamourer/Api/Api/IGlamourerApi.cs b/Glamourer/Api/Api/IGlamourerApi.cs deleted file mode 100644 index c28410b..0000000 --- a/Glamourer/Api/Api/IGlamourerApi.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Glamourer.Api.Api; - -public interface IGlamourerApi : IGlamourerApiBase -{ - public IGlamourerApiDesigns Designs { get; } - public IGlamourerApiItems Items { get; } - public IGlamourerApiState State { get; } -} \ No newline at end of file diff --git a/Glamourer/Api/Api/IGlamourerApiBase.cs b/Glamourer/Api/Api/IGlamourerApiBase.cs deleted file mode 100644 index b52db45..0000000 --- a/Glamourer/Api/Api/IGlamourerApiBase.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Glamourer.Api.Api; - -public interface IGlamourerApiBase -{ - public (int Major, int Minor) ApiVersion { get; } -} diff --git a/Glamourer/Api/Api/IGlamourerApiDesigns.cs b/Glamourer/Api/Api/IGlamourerApiDesigns.cs deleted file mode 100644 index f4d7184..0000000 --- a/Glamourer/Api/Api/IGlamourerApiDesigns.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Glamourer.Api.Enums; - -namespace Glamourer.Api.Api; - -public interface IGlamourerApiDesigns -{ - public Dictionary GetDesignList(); - - public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags); - - public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags); -} diff --git a/Glamourer/Api/Api/IGlamourerApiItems.cs b/Glamourer/Api/Api/IGlamourerApiItems.cs deleted file mode 100644 index 25bdcee..0000000 --- a/Glamourer/Api/Api/IGlamourerApiItems.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Glamourer.Api.Enums; - -namespace Glamourer.Api.Api; - -public interface IGlamourerApiItems -{ - public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot apiSlot, ulong itemId, byte stain, uint key, ApplyFlag flags); - public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags); -} diff --git a/Glamourer/Api/Api/IGlamourerApiState.cs b/Glamourer/Api/Api/IGlamourerApiState.cs deleted file mode 100644 index 2443701..0000000 --- a/Glamourer/Api/Api/IGlamourerApiState.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Glamourer.Api.Enums; -using Newtonsoft.Json.Linq; - -namespace Glamourer.Api.Api; - -public interface IGlamourerApiState -{ - public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key); - public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key); - - public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags); - - public GlamourerApiEc ApplyStateName(object state, string objectName, uint key, ApplyFlag flags); - - public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags); - public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags); - - public GlamourerApiEc UnlockState(int objectIndex, uint key); - public GlamourerApiEc UnlockStateName(string objectName, uint key); - public int UnlockAll(uint key); - - public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags); - public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags); - - public event Action? StateChanged; - - public event Action? GPoseChanged; -} diff --git a/Glamourer/Api/Enums/ApplyFlag.cs b/Glamourer/Api/Enums/ApplyFlag.cs deleted file mode 100644 index 0008e96..0000000 --- a/Glamourer/Api/Enums/ApplyFlag.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Glamourer.Api.Enums; - -[Flags] -public enum ApplyFlag : ulong -{ - Once = 0x01, - Equipment = 0x02, - Customization = 0x04, - Lock = 0x08, -} - -public static class ApplyFlagEx -{ - public const ApplyFlag DesignDefault = ApplyFlag.Once | ApplyFlag.Equipment | ApplyFlag.Customization; - public const ApplyFlag StateDefault = ApplyFlag.Equipment | ApplyFlag.Customization | ApplyFlag.Lock; - public const ApplyFlag RevertDefault = ApplyFlag.Equipment | ApplyFlag.Customization; -} - -public enum ApiEquipSlot : byte -{ - Unknown = 0, - MainHand = 1, - OffHand = 2, - Head = 3, - Body = 4, - Hands = 5, - Legs = 7, - Feet = 8, - Ears = 9, - Neck = 10, - Wrists = 11, - RFinger = 12, - LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game. -} \ No newline at end of file diff --git a/Glamourer/Api/Enums/GlamourerApiEc.cs b/Glamourer/Api/Enums/GlamourerApiEc.cs deleted file mode 100644 index 086a2a5..0000000 --- a/Glamourer/Api/Enums/GlamourerApiEc.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Glamourer.Api.Enums; - -public enum GlamourerApiEc -{ - Success, - ActorNotFound, - ActorNotHuman, - DesignNotFound, - ItemInvalid, - InvalidKey, - InvalidState, - NothingDone, -} diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 98461c6..4be328c 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -1,8 +1,8 @@ -using Glamourer.Api.Api; -using OtterGui.Services; - -namespace Glamourer.Api; - +using Glamourer.Api.Api; +using OtterGui.Services; + +namespace Glamourer.Api; + public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; @@ -19,4 +19,4 @@ public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : public IGlamourerApiState State => state; -} +} diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 115142b..c2a674c 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Glamourer.Api.Api; +using Glamourer.Api.Helpers; using OtterGui.Services; -using Penumbra.Api.Helpers; namespace Glamourer.Api; diff --git a/Glamourer/Api/IpcSubscribers/Designs.cs b/Glamourer/Api/IpcSubscribers/Designs.cs deleted file mode 100644 index 8b31f6e..0000000 --- a/Glamourer/Api/IpcSubscribers/Designs.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api.Api; -using Glamourer.Api.Enums; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api.IpcSubscribers; - -/// -public sealed class GetDesignList(DalamudPluginInterface pi) - : FuncSubscriber>(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(GetDesignList)}"; - - /// - public new Dictionary Invoke() - => base.Invoke(); - - /// Create a provider. - public static FuncProvider> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) - => new(pi, Label, api.GetDesignList); -} - -/// -public sealed class ApplyDesign(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(ApplyDesign)}"; - - /// - public GlamourerApiEc Invoke(Guid designId, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) - => (GlamourerApiEc)Invoke(designId, objectIndex, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) - => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesign(a, b, c, (ApplyFlag)d)); -} - -/// -public sealed class ApplyDesignName(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(ApplyDesignName)}"; - - /// - public GlamourerApiEc Invoke(Guid designId, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) - => (GlamourerApiEc)Invoke(designId, objectName, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) - => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesignName(a, b, c, (ApplyFlag)d)); -} diff --git a/Glamourer/Api/IpcSubscribers/Items.cs b/Glamourer/Api/IpcSubscribers/Items.cs deleted file mode 100644 index 2c9139c..0000000 --- a/Glamourer/Api/IpcSubscribers/Items.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api.Api; -using Glamourer.Api.Enums; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Enums; - -namespace Glamourer.Api.IpcSubscribers; - -/// -public sealed class SetItem(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(SetItem)}"; - - /// - public GlamourerApiEc Invoke(int objectIndex, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) - => (GlamourerApiEc)Invoke(objectIndex, (byte)slot, itemId, stain, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) - => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItem(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); -} - -/// -public sealed class SetItemName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(SetItemName)}"; - - /// - public GlamourerApiEc Invoke(string objectName, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) - => (GlamourerApiEc)Invoke(objectName, (byte)slot, itemId, stain, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) - => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItemName(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); -} diff --git a/Glamourer/Api/IpcSubscribers/PluginState.cs b/Glamourer/Api/IpcSubscribers/PluginState.cs deleted file mode 100644 index 107c51a..0000000 --- a/Glamourer/Api/IpcSubscribers/PluginState.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api.Api; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api.IpcSubscribers; - -/// -public sealed class ApiVersion(DalamudPluginInterface pi) - : FuncSubscriber<(int, int)>(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(ApiVersion)}"; - - /// - public new (int Major, int Minor) Invoke() - => base.Invoke(); - - /// Create a provider. - public static FuncProvider<(int, int)> Provider(DalamudPluginInterface pi, IGlamourerApiBase api) - => new(pi, Label, () => api.ApiVersion); -} - -/// Triggered when the Glamourer API is initialized and ready. -public static class Initialized -{ - /// The label. - public const string Label = $"Glamourer.{nameof(Initialized)}"; - - /// Create a new event subscriber. - public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) - => new(pi, Label, actions); - - /// Create a provider. - public static EventProvider Provider(DalamudPluginInterface pi) - => new(pi, Label); -} - -/// Triggered when the Glamourer API is fully disposed and unavailable. -public static class Disposed -{ - /// The label. - public const string Label = $"Glamourer.{nameof(Disposed)}"; - - /// Create a new event subscriber. - public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) - => new(pi, Label, actions); - - /// Create a provider. - public static EventProvider Provider(DalamudPluginInterface pi) - => new(pi, Label); -} diff --git a/Glamourer/Api/IpcSubscribers/State.cs b/Glamourer/Api/IpcSubscribers/State.cs deleted file mode 100644 index e523c10..0000000 --- a/Glamourer/Api/IpcSubscribers/State.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api.Api; -using Glamourer.Api.Enums; -using Newtonsoft.Json.Linq; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api.IpcSubscribers; - -/// -public sealed class GetState(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(GetState)}"; - - /// - public new (GlamourerApiEc, JObject?) Invoke(int objectIndex, uint key = 0) - { - var (ec, data) = base.Invoke(objectIndex, key); - return ((GlamourerApiEc)ec, data); - } - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b) => - { - var (ec, data) = api.GetState(a, b); - return ((int)ec, data); - }); -} - -/// -public sealed class GetStateName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(GetStateName)}"; - - /// - public new (GlamourerApiEc, JObject?) Invoke(string objectName, uint key = 0) - { - var (ec, data) = base.Invoke(objectName, key); - return ((GlamourerApiEc)ec, data); - } - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (i, k) => - { - var (ec, data) = api.GetStateName(i, k); - return ((int)ec, data); - }); -} - -/// -public sealed class ApplyState(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(ApplyState)}"; - - /// - public GlamourerApiEc Invoke(JObject state, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) - => (GlamourerApiEc)Invoke(state, objectIndex, key, (ulong)flags); - - /// - public GlamourerApiEc Invoke(string base64State, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) - => (GlamourerApiEc)Invoke(base64State, objectIndex, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c, d) => (int)api.ApplyState(a, b, c, (ApplyFlag)d)); -} - -/// -public sealed class ApplyStateName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(ApplyStateName)}"; - - /// - public GlamourerApiEc Invoke(JObject state, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) - => (GlamourerApiEc)Invoke(state, objectName, key, (ulong)flags); - - /// - public GlamourerApiEc Invoke(string base64State, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) - => (GlamourerApiEc)Invoke(base64State, objectName, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c, d) => (int)api.ApplyStateName(a, b, c, (ApplyFlag)d)); -} - -/// -public sealed class RevertState(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(RevertState)}"; - - /// - public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) - => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c) => (int)api.RevertState(a, b, (ApplyFlag)c)); -} - -/// -public sealed class RevertStateName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(RevertStateName)}"; - - /// - public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) - => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c) => (int)api.RevertStateName(a, b, (ApplyFlag)c)); -} - -/// -public sealed class UnlockState(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(UnlockState)}"; - - /// - public new GlamourerApiEc Invoke(int objectIndex, uint key = 0) - => (GlamourerApiEc)base.Invoke(objectIndex, key); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b) => (int)api.UnlockState(a, b)); -} - -/// -public sealed class UnlockStateName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(UnlockStateName)}"; - - /// - public new GlamourerApiEc Invoke(string objectName, uint key = 0) - => (GlamourerApiEc)base.Invoke(objectName, key); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b) => (int)api.UnlockStateName(a, b)); -} - -/// -public sealed class UnlockAll(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(UnlockAll)}"; - - /// - public new int Invoke(uint key) - => base.Invoke(key); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, api.UnlockAll); -} - -/// -public sealed class RevertToAutomation(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(RevertToAutomation)}"; - - /// - public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) - => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c) => (int)api.RevertToAutomation(a, b, (ApplyFlag)c)); -} - -/// -public sealed class RevertToAutomationName(DalamudPluginInterface pi) - : FuncSubscriber(pi, Label) -{ - /// The label. - public const string Label = $"Glamourer.{nameof(RevertToAutomationName)}"; - - /// - public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) - => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); - - /// Create a provider. - public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (a, b, c) => (int)api.RevertToAutomationName(a, b, (ApplyFlag)c)); -} - -/// -public static class StateChanged -{ - /// The label. - public const string Label = $"Penumbra.{nameof(StateChanged)}"; - - /// Create a new event subscriber. - public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) - => new(pi, Label, actions); - - /// Create a provider. - public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (t => api.StateChanged += t, t => api.StateChanged -= t)); -} - -/// -public static class GPoseChanged -{ - /// The label. - public const string Label = $"Penumbra.{nameof(GPoseChanged)}"; - - /// Create a new event subscriber. - public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) - => new(pi, Label, actions); - - /// Create a provider. - public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) - => new(pi, Label, (t => api.GPoseChanged += t, t => api.GPoseChanged -= t)); -} diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index d50c24e..9a1b95b 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -83,6 +83,7 @@ + diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index ec75998..3d61df7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -13,14 +13,14 @@ namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService { - private int _gameObjectIndex; - private string _gameObjectName = string.Empty; - private uint _key; - private ApplyFlag _flags = ApplyFlagEx.DesignDefault; - private CustomItemId _customItemId; - private StainId _stainId; - private EquipSlot _slot = EquipSlot.Head; - private GlamourerApiEc _lastError; + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private CustomItemId _customItemId; + private StainId _stainId; + private EquipSlot _slot = EquipSlot.Head; + private GlamourerApiEc _lastError; public void Draw() { @@ -40,11 +40,13 @@ public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService IpcTesterHelpers.DrawIntro(SetItem.Label); if (ImGui.Button("Set##Idx")) - _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _flags); IpcTesterHelpers.DrawIntro(SetItemName.Label); if (ImGui.Button("Set##Name")) - _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _flags); } private void DrawItemInput() diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 5898177..bcf7454 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api.Enums; +using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using Glamourer.Designs; using ImGuiNET; @@ -10,7 +11,6 @@ using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; using OtterGui.Services; -using Penumbra.Api.Helpers; using Penumbra.GameData.Interop; using Penumbra.String; From a722abb14100ee3c3ef580be63ac65215f6ca9fb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:35:06 +0200 Subject: [PATCH 14/45] Add Api Submodule. --- .gitmodules | 4 ++++ Glamourer.Api | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 7203d22..856d5b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,7 @@ path = Penumbra.Api url = https://github.com/Ottermandias/Penumbra.Api.git branch = main +[submodule "Glamourer.Api"] + path = Glamourer.Api + url = git@github.com:Ottermandias/Glamourer.Api.git + branch = main diff --git a/Glamourer.Api b/Glamourer.Api index 0c8578c..aaa1435 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 0c8578cfa12bf0591ed204fd89b30b66719f678f +Subproject commit aaa1435b43f7e714e05e802ac4524a6d93353be6 From ee78a2a98360b627d50ac54a33c1a358fac716bb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:37:19 +0200 Subject: [PATCH 15/45] Fix API Version. --- Glamourer.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index aaa1435..f584820 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit aaa1435b43f7e714e05e802ac4524a6d93353be6 +Subproject commit f58482079a2ebe6b2958ff2432b245bcf180d3c2 From fb64315d517fb9a764ae515902f4afbe6cb4171b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Apr 2024 16:54:02 +0200 Subject: [PATCH 16/45] Add documentation to API. --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 6 +++--- Glamourer/Api/ItemsApi.cs | 6 +++--- Glamourer/Api/StateApi.cs | 40 ++++++++++++++++++++++++------------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index f584820..1529863 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit f58482079a2ebe6b2958ff2432b245bcf180d3c2 +Subproject commit 1529863ecb33d85ea88dc2cf3c749a245b11cf55 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 16e0ca9..ee49bd5 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -39,16 +39,16 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager.ApplyDesign(state, design, settings); } - public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags) + public GlamourerApiEc ApplyDesignName(Guid designId, string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Design", designId, "Name", objectName, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Design", designId, "Name", playerName, "Key", key, "Flags", flags); var design = designs.Designs.ByIdentifier(designId); if (design == null) return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args); var any = false; var anyUnlocked = false; - foreach (var state in helpers.FindStates(objectName)) + foreach (var state in helpers.FindStates(playerName)) { any = true; if (!state.CanUnlock(key)) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index baea51a..cda1980 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -32,9 +32,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } - public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + public GlamourerApiEc SetItemName(string playerName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", objectName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); if (!ResolveItem(slot, itemId, out var item)) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); @@ -42,7 +42,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager var anyHuman = false; var anyFound = false; var anyUnlocked = false; - foreach (var state in helpers.FindStates(objectName)) + foreach (var state in helpers.FindStates(playerName)) { anyFound = true; if (!state.ModelData.IsHuman) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index d540c7c..3e2fde4 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -54,8 +54,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key) => Convert(_helpers.FindState(objectIndex), key); - public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key) - => Convert(_helpers.FindStates(objectName).FirstOrDefault(), key); + public (GlamourerApiEc, JObject?) GetStateName(string playerName, uint key) + => Convert(_helpers.FindStates(playerName).FirstOrDefault(), key); + + public (GlamourerApiEc, string?) GetStateBase64(int objectIndex, uint key) + => ConvertBase64(_helpers.FindState(objectIndex), key); + + public (GlamourerApiEc, string?) GetStateBase64Name(string objectName, uint key) + => ConvertBase64(_helpers.FindStates(objectName).FirstOrDefault(), key); public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags) { @@ -76,13 +82,13 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc ApplyStateName(object applyState, string objectName, uint key, ApplyFlag flags) + public GlamourerApiEc ApplyStateName(object applyState, string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); if (Convert(applyState, flags, out var version) is not { } design) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); - var states = _helpers.FindExistingStates(objectName); + var states = _helpers.FindExistingStates(playerName); var any = false; var anyUnlocked = false; @@ -129,10 +135,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags) + public GlamourerApiEc RevertStateName(string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); - var states = _helpers.FindExistingStates(objectName); + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(playerName); var any = false; var anyUnlocked = false; @@ -170,10 +176,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc UnlockStateName(string objectName, uint key) + public GlamourerApiEc UnlockStateName(string playerName, uint key) { - var args = ApiHelpers.Args("Name", objectName, "Key", key); - var states = _helpers.FindExistingStates(objectName); + var args = ApiHelpers.Args("Name", playerName, "Key", key); + var states = _helpers.FindExistingStates(playerName); var any = false; var anyUnlocked = false; @@ -211,10 +217,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags) + public GlamourerApiEc RevertToAutomationName(string playerName, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); - var states = _helpers.FindExistingStates(objectName); + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(playerName); var any = false; var anyUnlocked = false; @@ -303,6 +309,12 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config))); } + private (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key) + { + var (ec, jObj) = Convert(state, key); + return (ec, jObj != null ? DesignConverter.ToBase64(jObj) : null); + } + private DesignBase? Convert(object? state, ApplyFlag flags, out byte version) { version = DesignConverter.Version; From efd51b0b5e493d54a5ec7ff93accb81534460578 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Apr 2024 17:31:17 +0200 Subject: [PATCH 17/45] Add Provider and tester for Base64. --- Glamourer.Api | 2 +- Glamourer/Api/IpcProviders.cs | 2 + .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 194 ------------------ .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 32 ++- 4 files changed, 31 insertions(+), 199 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 1529863..ca919c3 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 1529863ecb33d85ea88dc2cf3c749a245b11cf55 +Subproject commit ca919c3f8982ca9990b909a225488ea20a119625 diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index c2a674c..68ce562 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -29,6 +29,8 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetState.Provider(pi, api.State), IpcSubscribers.GetStateName.Provider(pi, api.State), + IpcSubscribers.GetStateBase64.Provider(pi, api.State), + IpcSubscribers.GetStateBase64Name.Provider(pi, api.State), IpcSubscribers.ApplyState.Provider(pi, api.State), IpcSubscribers.ApplyStateName.Provider(pi, api.State), IpcSubscribers.RevertState.Provider(pi, api.State), diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 62203ac..5e6f4a2 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -1,16 +1,8 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.System.Framework; -using Glamourer.Api; -using Glamourer.Api.Enums; using Glamourer.Api.IpcSubscribers; -using Glamourer.Interop; using ImGuiNET; -using OtterGui; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Gui; using Penumbra.GameData.Gui.Debug; -using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; @@ -49,192 +41,6 @@ public class IpcTesterPanel( { Glamourer.Log.Error($"Error during IPC Tests:\n{e}"); } - //ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - //ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); - //ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); - //ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); - //DrawItemInput(); - //using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - //if (!table) - // return; - // - //ImGuiUtil.DrawTableColumn(); - //ImGui.TableNextColumn(); - //var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName); - //if (base64 != null) - // ImGuiUtil.CopyOnClickSelectable(base64); - //else - // ImGui.TextUnformatted("Error"); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); - //ImGui.TableNextColumn(); - //base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - //if (base64 != null) - // ImGuiUtil.CopyOnClickSelectable(base64); - //else - // ImGui.TextUnformatted("Error"); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); - //ImGui.TableNextColumn(); - //var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - //if (base64Locked != null) - // ImGuiUtil.CopyOnClickSelectable(base64Locked); - //else - // ImGui.TextUnformatted("Error"); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Revert##Name")) - // GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Revert##Character")) - // GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##AllName")) - // GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply Once##AllName")) - // GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##AllCharacter")) - // GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply Once##AllCharacter")) - // GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) - // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##EquipName")) - // GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##EquipCharacter")) - // GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) - // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##CustomizeName")) - // GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##CustomizeCharacter")) - // GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) - // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) - // GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once)) - // GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) - // GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) - // .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) - // GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) - // .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Apply With Lock##CustomizeCharacter")) - // GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) - // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Unlock##CustomizeCharacter")) - // GlamourerIpc.UnlockSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Unlock All##CustomizeCharacter")) - // GlamourerIpc.UnlockAllSubscriber(_pluginInterface) - // .Invoke(1337); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Revert##CustomizeCharacter")) - // GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - // - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); - //ImGui.TableNextColumn(); - //var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) - // .Invoke(); - //if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList")) - // ImGui.SetClipboardText(string.Join("\n", designList)); - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Set##SetItem")) - // _setItemEc = (GlamourerApiEc)GlamourerIpc.SetItemSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - //if (_setItemEc != GlamourerApiEc.Success) - //{ - // ImGui.SameLine(); - // ImGui.TextUnformatted(_setItemEc.ToString()); - //} - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Set Once##SetItem")) - // _setItemOnceEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) - // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - //if (_setItemOnceEc != GlamourerApiEc.Success) - //{ - // ImGui.SameLine(); - // ImGui.TextUnformatted(_setItemOnceEc.ToString()); - //} - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Set##SetItemByActorName")) - // _setItemByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) - // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - //if (_setItemByActorNameEc != GlamourerApiEc.Success) - //{ - // ImGui.SameLine(); - // ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); - //} - // - //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName); - //ImGui.TableNextColumn(); - //if (ImGui.Button("Set Once##SetItemByActorName")) - // _setItemOnceByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface) - // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - //if (_setItemOnceByActorNameEc != GlamourerApiEc.Success) - //{ - // ImGui.SameLine(); - // ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString()); - //} } private void Subscribe() diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index bcf7454..28b9506 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -28,6 +28,7 @@ public class StateIpcTester : IUiService, IDisposable private JObject? _state; private string? _stateString; private string _base64State = string.Empty; + private string? _getStateString; public readonly EventSubscriber StateChanged; private nint _lastStateChangeActor; @@ -92,6 +93,22 @@ public class StateIpcTester : IUiService, IDisposable ImGui.OpenPopup("State"); } + IpcTesterHelpers.DrawIntro(GetStateBase64.Label); + if (ImGui.Button("Get##Base64Idx")) + { + (_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key); + _stateString = _getStateString ?? "No State Available"; + ImGui.OpenPopup("State"); + } + + IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label); + if (ImGui.Button("Get##Base64Idx")) + { + (_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key); + _stateString = _getStateString ?? "No State Available"; + ImGui.OpenPopup("State"); + } + 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); @@ -140,17 +157,24 @@ public class StateIpcTester : IUiService, IDisposable private void DrawStatePopup() { ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500)); + if (_stateString == null) + return; + using var p = ImRaii.Popup("State"); if (!p) return; if (ImGui.Button("Copy to Clipboard")) ImGui.SetClipboardText(_stateString); - ImGui.SameLine(); - if (ImGui.Button("Copy as Base64") && _state != null) - ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + if (_stateString[0] is '{') + { + ImGui.SameLine(); + if (ImGui.Button("Copy as Base64") && _state != null) + ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + } + using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGui.TextUnformatted(_stateString); + ImGuiUtil.TextWrapped(_stateString ?? string.Empty); if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) ImGui.CloseCurrentPopup(); From f9491532920a345175147ffd723c7453c9f939f8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Apr 2024 23:29:54 +0200 Subject: [PATCH 18/45] Fix Apply Dye checkbox tooltip. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 33c5378..b21f700 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -579,7 +579,7 @@ public class EquipmentDrawer private static void DrawApplyStain(in EquipDrawData data) { - if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain, + if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain, out var enabled, data.Locked)) data.SetApplyStain(enabled); From 4900ccb75aab642cafed4818cfb225315ddee3ca Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Apr 2024 23:30:22 +0200 Subject: [PATCH 19/45] Make automatically applied offhands due to setting also apply mainhand dye. --- Glamourer/State/StateEditor.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 9bae385..30a61ce 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -81,7 +81,7 @@ public class StateEditor( item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, settings); + 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")}.]"); @@ -114,7 +114,7 @@ public class StateEditor( item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, settings); + ApplyMainhandPeriphery(state, item, stain, 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 {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); @@ -389,18 +389,19 @@ public class StateEditor( /// Apply offhand item and potentially gauntlets if configured. - private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings) + private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainId? newStain, ApplySettings settings) { if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); + var stain = newStain ?? state.ModelData.Stain(EquipSlot.MainHand); if (offhand.Valid) - ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), settings); + ChangeEquip(state, EquipSlot.OffHand, offhand, stain, settings); if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets)) ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands), - state.ModelData.Stain(EquipSlot.Hands), settings); + stain, settings); } } From dfb3ef3d79ea61824911fc03537092434c9ea031 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Apr 2024 23:30:45 +0200 Subject: [PATCH 20/45] Add some copy functionality for mod associations. --- Glamourer/Designs/DesignManager.cs | 11 +-- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 73 +++++++++++++------ 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index e880feb..03f0590 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -273,13 +273,13 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); } - public void UpdateMod(Design design, Mod mod, ModSettings settings) { - if (!design.AssociatedMods.ContainsKey(mod)) - return; + /// Add or update an associated mod to a design. + public void UpdateMod(Design design, Mod mod, ModSettings settings) + { design.AssociatedMods[mod] = settings; design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}."); + Glamourer.Log.Debug($"Updated (or added) associated mod {mod.DirectoryName} from design {design.Identifier}."); DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); } @@ -302,7 +302,8 @@ public sealed class DesignManager : DesignEditor design.QuickDesign = value; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar."); + Glamourer.Log.Debug( + $"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar."); DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, value); } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 164fe78..1915241 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -13,7 +13,8 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) { - private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); + private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); + private (Mod, ModSettings)[]? _copy; public void Draw() { @@ -28,6 +29,36 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect DrawApplyAllButton(); DrawTable(); + DrawCopyButtons(); + } + + private void DrawCopyButtons() + { + var size = new Vector2((ImGui.GetContentRegionAvail().X - 2 * ImGui.GetStyle().ItemSpacing.X) / 3, 0); + if (ImGui.Button("Copy All to Clipboard", size)) + _copy = selector.Selected!.AssociatedMods.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + + ImGui.SameLine(); + + if (ImGuiUtil.DrawDisabledButton("Add from Clipboard", size, + _copy != null + ? $"Add {_copy.Length} mod association(s) from clipboard." + : "Copy some mod associations to the clipboard, first.", _copy == null)) + foreach (var (mod, setting) in _copy!) + manager.UpdateMod(selector.Selected!, mod, setting); + + ImGui.SameLine(); + + if (ImGuiUtil.DrawDisabledButton("Set from Clipboard", size, + _copy != null + ? $"Set {_copy.Length} mod association(s) from clipboard and discard existing." + : "Copy some mod associations to the clipboard, first.", _copy == null)) + { + while (selector.Selected!.AssociatedMods.Count > 0) + manager.RemoveMod(selector.Selected!, selector.Selected!.AssociatedMods.Keys[0]); + foreach (var (mod, setting) in _copy!) + manager.AddMod(selector.Selected!, mod, setting); + } } private void DrawApplyAllButton() @@ -55,17 +86,16 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawTable() { - using var table = ImRaii.Table("Mods", 7, ImGuiTableFlags.RowBg); + using var table = ImRaii.Table("Mods", 5, ImGuiTableFlags.RowBg); if (!table) return; - ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); - ImGui.TableSetupColumn("##Update", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); + ImGui.TableSetupColumn("##Buttons", ImGuiTableColumnFlags.WidthFixed, + ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2); ImGui.TableSetupColumn("Mod Name", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("Directory Name", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("State").X); ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Priority").X); - ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X); + ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Applym").X); ImGui.TableHeadersRow(); Mod? removedMod = null; @@ -94,14 +124,20 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect removedMod = null; updatedMod = null; ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Delete this mod from associations", false, true)) + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, + "Delete this mod from associations.", false, true)) removedMod = mod; - ImGui.TableNextColumn(); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Update the settings of this mod association", false, true); + ImGui.SameLine(0, spacing); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, + "Copy this mod setting to clipboard.", false, true)) + _copy = [(mod, settings)]; + ImGui.SameLine(0, spacing); + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), buttonSize, + "Update the settings of this mod association.", false, true); if (ImGui.IsItemHovered()) { var newSettings = penumbra.GetModSettings(mod); @@ -134,15 +170,11 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect } ImGui.TableNextColumn(); - var selected = ImGui.Selectable($"{mod.Name}##name"); - var hovered = ImGui.IsItemHovered(); - ImGui.TableNextColumn(); - selected |= ImGui.Selectable($"{mod.DirectoryName}##directory"); - hovered |= ImGui.IsItemHovered(); - if (selected) + + if (ImGui.Selectable($"{mod.Name}##name")) penumbra.OpenModPage(mod); - if (hovered) - ImGui.SetTooltip("Click to open mod page in Penumbra."); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra."); ImGui.TableNextColumn(); using (var font = ImRaii.PushFont(UiBuilder.IconFont)) { @@ -152,7 +184,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImGui.TableNextColumn(); ImGuiUtil.RightAlign(settings.Priority.ToString()); ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, + if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) { var text = penumbra.SetMod(mod, settings); @@ -188,7 +220,6 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect { var currentName = _modCombo.CurrentSelection.Mod.Name; ImGui.TableNextColumn(); - ImGui.TableNextColumn(); var tt = currentName.IsNullOrEmpty() ? "Please select a mod first." : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) From e50474f12d988902f1b4474111e8c35db464348b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Apr 2024 18:16:33 +0200 Subject: [PATCH 21/45] Fix Eye labels. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index fe9d563..2a34969 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fe9d563d9845630673cf098f7a6bfbd26e600fb4 +Subproject commit 2a349691de9ae9bd10f6d3a247a1227ddfea97b3 From 552338e5b5f47d0b6ca83b40b0203f2071e8cd91 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Apr 2024 18:16:48 +0200 Subject: [PATCH 22/45] Improve IPC Providers. --- Glamourer/Api/IpcProviders.cs | 1 + Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 68ce562..62e3ca1 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -18,6 +18,7 @@ public sealed class IpcProviders : IDisposable, IApiService _initializedProvider = IpcSubscribers.Initialized.Provider(pi); _providers = [ + new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility IpcSubscribers.ApiVersion.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 28b9506..f210b0f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -46,6 +46,8 @@ public class StateIpcTester : IUiService, IDisposable _pluginInterface = pluginInterface; StateChanged = Api.IpcSubscribers.StateChanged.Subscriber(_pluginInterface, OnStateChanged); GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + StateChanged.Disable(); + GPoseChanged.Disable(); } public void Dispose() From 78d7aef13f4077af0a08a3c582a5bb5bd4942225 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Apr 2024 01:05:15 +0200 Subject: [PATCH 23/45] Add height display in real-world units. --- Glamourer/Configuration.cs | 75 +++++++++++-------- .../CustomizationDrawer.Simple.cs | 21 ++++++ .../Gui/Customization/CustomizationDrawer.cs | 14 +++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 36 +++++++++ Glamourer/Services/HeightService.cs | 23 ++++++ 5 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 Glamourer/Services/HeightService.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index ce2ed08..dbd245f 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -14,43 +14,54 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Glamourer; +public enum HeightDisplayType +{ + None, + Centimetre, + Metre, + Wrong, + WrongFoot, +} + 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 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; - 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 AllowDoubleClickToApply { get; set; } = false; - public bool RespectManualOnAutomationUpdate { get; set; } = false; - 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 ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; + 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 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; + 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 AllowDoubleClickToApply { get; set; } = false; + public bool RespectManualOnAutomationUpdate { get; set; } = false; + + public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; + 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 ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public QdbButtons QdbButtons { get; set; } = QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 3e2b453..8668dbe 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -29,6 +29,27 @@ public partial class CustomizationDrawer ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(_currentOption); + if (_currentIndex is CustomizeIndex.Height) + DrawHeight(); + } + + private void DrawHeight() + { + if (_config.HeightDisplayType is HeightDisplayType.None) + return; + + var height = _heightService.Height(_customize); + ImGui.SameLine(); + + var heightString = _config.HeightDisplayType switch + { + HeightDisplayType.Centimetre => $"({height * 100:F1} cm)", + HeightDisplayType.Metre => $"({height:F2} m)", + HeightDisplayType.Wrong => $"({height * 100 / 2.539:F1} in)", + HeightDisplayType.WrongFoot => $"({(int)(height * 100 / 2.539 / 12)}'{(int)(height * 100 / 2.539) % 12}'')", + _ => $"({height})", + }; + ImGui.TextUnformatted(heightString); } private void DrawPercentageSlider() diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 8cb7f91..60251df 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -12,7 +12,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites) +public partial class CustomizationDrawer( + DalamudPluginInterface pi, + CustomizeService _service, + CodeService _codes, + Configuration _config, + FavoriteManager _favorites, + HeightService _heightService) : IDisposable { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); @@ -20,8 +26,8 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer private Exception? _terminate; - private CustomizeArray _customize = CustomizeArray.Default; - private CustomizeSet _set = null!; + private CustomizeArray _customize = CustomizeArray.Default; + private CustomizeSet _set = null!; public CustomizeArray Customize => _customize; @@ -46,7 +52,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { - _withApply = false; + _withApply = false; Init(current, locked, lockedRedraw); return DrawInternal(); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 67ecda7..e9ac60f 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -161,6 +161,7 @@ public class SettingsTab( Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", 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.", !config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v); @@ -441,4 +442,39 @@ public class SettingsTab( ImGui.TextUnformatted("Rename Fields in Design Context Menu"); ImGuiUtil.HoverTooltip(tt); } + + private void DrawHeightUnitSettings() + { + ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); + using (var combo = ImRaii.Combo("##heightUnit", HeightDisplayTypeName(config.HeightDisplayType))) + { + if (combo) + foreach (var type in Enum.GetValues()) + { + if (ImGui.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType) + { + config.HeightDisplayType = type; + config.Save(); + } + } + } + + ImGui.SameLine(); + 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); + } + + private string 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'')", + _ => string.Empty, + }; } diff --git a/Glamourer/Services/HeightService.cs b/Glamourer/Services/HeightService.cs new file mode 100644 index 0000000..48f0dd6 --- /dev/null +++ b/Glamourer/Services/HeightService.cs @@ -0,0 +1,23 @@ +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Services; + +public unsafe class HeightService : IService +{ + [Signature("E8 ?? ?? ?? FF 48 8B 0D ?? ?? ?? ?? 0F 28 F0")] + private readonly delegate* unmanaged[Stdcall] _calculateHeight = null!; + + public HeightService(IGameInteropProvider interop) + => interop.InitializeFromAttributes(this); + + public float Height(CustomizeValue height, SubRace clan, Gender gender, CustomizeValue bodyType) + => _calculateHeight(CharacterUtility.Instance(), height.Value, (byte)clan, (byte)((byte)gender - 1), bodyType.Value); + + public float Height(in CustomizeArray customize) + => Height(customize[CustomizeIndex.Height], customize.Clan, customize.Gender, customize.BodyType); +} From e0447b1ed464db9af68bbbd60883bc7e671e9bb5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 Apr 2024 15:12:58 +0200 Subject: [PATCH 24/45] Fix height display. --- Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 8668dbe..5bb98c9 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -43,11 +43,11 @@ public partial class CustomizationDrawer var heightString = _config.HeightDisplayType switch { - HeightDisplayType.Centimetre => $"({height * 100:F1} cm)", - HeightDisplayType.Metre => $"({height:F2} m)", - HeightDisplayType.Wrong => $"({height * 100 / 2.539:F1} in)", + HeightDisplayType.Centimetre => FormattableString.Invariant($"({height * 100:F1} cm)"), + HeightDisplayType.Metre => FormattableString.Invariant($"({height:F2} m)"), + HeightDisplayType.Wrong => FormattableString.Invariant($"({height * 100 / 2.539:F1} in)"), HeightDisplayType.WrongFoot => $"({(int)(height * 100 / 2.539 / 12)}'{(int)(height * 100 / 2.539) % 12}'')", - _ => $"({height})", + _ => FormattableString.Invariant($"({height})"), }; ImGui.TextUnformatted(heightString); } From e8096f6e009a612f410428254642e1ed6c7ec16f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 Apr 2024 15:13:09 +0200 Subject: [PATCH 25/45] Add legacy IPC. --- Glamourer.Api | 2 +- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + Penumbra.Api | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index ca919c3..a111b1a 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit ca919c3f8982ca9990b909a225488ea20a119625 +Subproject commit a111b1ac2aea59a66861b3132077308a0b75f015 diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 4be328c..e08537a 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -5,7 +5,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { - public const int CurrentApiVersionMajor = 1; + public const int CurrentApiVersionMajor = 2; public const int CurrentApiVersionMinor = 0; public (int Major, int Minor) ApiVersion diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 62e3ca1..3852fd6 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -19,6 +19,7 @@ public sealed class IpcProviders : IDisposable, IApiService _providers = [ new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility + new FuncProvider(pi, "Glamourer.ApiVersion", () => api.ApiVersion.Major), // backward compatibility IpcSubscribers.ApiVersion.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), diff --git a/Penumbra.Api b/Penumbra.Api index 0c8578c..590629d 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 0c8578cfa12bf0591ed204fd89b30b66719f678f +Subproject commit 590629df33f9ad92baddd1d65ec8c986f18d608a From 9d18707cb7a0e8fdeb790c56ebb0507ddb1d2213 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 Apr 2024 15:20:18 +0200 Subject: [PATCH 26/45] Update API. --- Glamourer.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index a111b1a..30cdd0a 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit a111b1ac2aea59a66861b3132077308a0b75f015 +Subproject commit 30cdd0a2386045b84f3cfdde483a1ffe60441f05 From 8fff09f92ebbbee6ea9dc5814767e57ce829b76a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 2 May 2024 00:59:03 +0200 Subject: [PATCH 27/45] Some compatibility --- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 6 ++- Glamourer/Interop/InventoryService.cs | 48 +++++++++---------- Glamourer/Interop/ScalingService.cs | 8 ++-- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 566ad52..c557064 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -82,7 +82,9 @@ public unsafe class ModelEvaluationPanel( ImGuiUtil.DrawTableColumn("Scale"); ImGuiUtil.DrawTableColumn(actor.Valid ? actor.AsObject->Scale.ToString(CultureInfo.InvariantCulture) : "No Character"); ImGuiUtil.DrawTableColumn(model.Valid ? model.AsDrawObject->Object.Scale.ToString() : "No Model"); - ImGuiUtil.DrawTableColumn(model.IsCharacterBase ? $"{*(float*)(model.Address + 0x270)} {*(float*)(model.Address + 0x274)}" : "No CharacterBase"); + ImGuiUtil.DrawTableColumn(model.IsCharacterBase + ? $"{*(float*)(model.Address + 0x270)} {*(float*)(model.Address + 0x274)}" + : "No CharacterBase"); } private void DrawParameters(Actor actor, Model model) @@ -229,7 +231,7 @@ public unsafe class ModelEvaluationPanel( ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData : new CustomizeArray(); var modelCustomize = model.IsHuman - ? *(CustomizeArray*)model.AsHuman->Customize.Data + ? *(CustomizeArray*)&model.AsHuman->Customize : new CustomizeArray(); foreach (var type in Enum.GetValues()) { diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6871ed9..9ad8737 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -73,18 +73,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->MainHand); - Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[10], ref entry->OffHand); - Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->Head); - Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->Body); - Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->Hands); - Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->Legs); - Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->Feet); - Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->Ears); - Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->Neck); - Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->Wrists); - Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->RingRight); - Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->RingLeft); + Add(EquipSlot.MainHand, plate.ItemIds[0], plate.StainIds[0], ref entry->ItemsSpan[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], plate.StainIds[1], ref entry->ItemsSpan[1]); + Add(EquipSlot.Head, plate.ItemIds[2], plate.StainIds[2], ref entry->ItemsSpan[2]); + Add(EquipSlot.Body, plate.ItemIds[3], plate.StainIds[3], ref entry->ItemsSpan[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], plate.StainIds[4], ref entry->ItemsSpan[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], plate.StainIds[5], ref entry->ItemsSpan[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], plate.StainIds[6], ref entry->ItemsSpan[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], plate.StainIds[7], ref entry->ItemsSpan[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], plate.StainIds[8], ref entry->ItemsSpan[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], plate.StainIds[9], ref entry->ItemsSpan[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], plate.StainIds[10], ref entry->ItemsSpan[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], plate.StainIds[11], ref entry->ItemsSpan[12]); } else { @@ -98,18 +98,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _itemList.Add((slot, FixId(item.ItemID), item.Stain)); } - Add(EquipSlot.MainHand, ref entry->MainHand); - Add(EquipSlot.OffHand, ref entry->OffHand); - Add(EquipSlot.Head, ref entry->Head); - Add(EquipSlot.Body, ref entry->Body); - Add(EquipSlot.Hands, ref entry->Hands); - Add(EquipSlot.Legs, ref entry->Legs); - Add(EquipSlot.Feet, ref entry->Feet); - Add(EquipSlot.Ears, ref entry->Ears); - Add(EquipSlot.Neck, ref entry->Neck); - Add(EquipSlot.Wrists, ref entry->Wrists); - Add(EquipSlot.RFinger, ref entry->RingRight); - Add(EquipSlot.LFinger, ref entry->RingLeft); + Add(EquipSlot.MainHand, ref entry->ItemsSpan[0]); + Add(EquipSlot.OffHand, ref entry->ItemsSpan[1]); + Add(EquipSlot.Head, ref entry->ItemsSpan[2]); + Add(EquipSlot.Body, ref entry->ItemsSpan[3]); + Add(EquipSlot.Hands, ref entry->ItemsSpan[5]); + Add(EquipSlot.Legs, ref entry->ItemsSpan[6]); + Add(EquipSlot.Feet, ref entry->ItemsSpan[7]); + Add(EquipSlot.Ears, ref entry->ItemsSpan[8]); + Add(EquipSlot.Neck, ref entry->ItemsSpan[9]); + Add(EquipSlot.Wrists, ref entry->ItemsSpan[10]); + Add(EquipSlot.RFinger, ref entry->ItemsSpan[11]); + Add(EquipSlot.LFinger, ref entry->ItemsSpan[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 1b55db2..66036d9 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -136,9 +136,9 @@ public unsafe class ScalingService : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static void SetHeightCustomize(Character* character, byte gender, byte bodyType, byte clan, byte height) { - character->DrawData.CustomizeData.Sex = gender; - character->DrawData.CustomizeData.BodyType = bodyType; - character->DrawData.CustomizeData.Tribe = clan; - character->DrawData.CustomizeData.Data[(int)CustomizeIndex.Height] = height; + character->DrawData.CustomizeData.Sex = gender; + character->DrawData.CustomizeData.BodyType = bodyType; + character->DrawData.CustomizeData.Tribe = clan; + character->DrawData.CustomizeData.Height = height; } } From 86c871fa81fc55b1d95216b96e0e9e598295d5c9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 2 May 2024 00:59:24 +0200 Subject: [PATCH 28/45] Add initial customize chat command. --- Glamourer/Services/CommandService.cs | 155 ++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 0616392..47ffa00 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Special; +using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -40,11 +41,12 @@ public class CommandService : IDisposable, IApiService private readonly ModSettingApplier _modApplier; private readonly ItemManager _items; private readonly RandomDesignGenerator _randomDesign; + private readonly CustomizeService _customizeService; 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) + ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService) { _commands = commands; _mainWindow = mainWindow; @@ -61,6 +63,7 @@ public class CommandService : IDisposable, IApiService _modApplier = modApplier; _items = items; _randomDesign = randomDesign; + _customizeService = customizeService; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -126,6 +129,7 @@ public class CommandService : IDisposable, IApiService "save" => SaveState(argument), "delete" => Delete(argument), "applyitem" => ApplyItem(argument), + "applycustomization" => ApplyCustomization(argument), _ => PrintHelp(argumentList[0]), }; } @@ -156,6 +160,9 @@ public class CommandService : IDisposable, IApiService .AddCommand("automation", "Change the state of automated design sets. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("applyitem", "Apply a specific item to a character. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddCommand("applycustomization", "Apply a specific customization value to a character. Use without arguments for help.") + .BuiltString); return true; } @@ -452,6 +459,152 @@ public class CommandService : IDisposable, IApiService return true; } + private bool ApplyCustomization(string arguments) + { + var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length is not 2) + return PrintCustomizationHelp(); + + var customizationSplit = split[0].Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (customizationSplit.Length < 2) + return PrintCustomizationHelp(); + + if (!Enum.TryParse(customizationSplit[0], true, out CustomizeIndex customizeIndex) + || !CustomizationExtensions.AllBasic.Contains(customizeIndex)) + { + if (!int.TryParse(customizationSplit[0], out var customizeInt) + || customizeInt < 0 + || customizeInt >= CustomizationExtensions.AllBasic.Length) + { + _chat.Print(new SeStringBuilder().AddText("The customization type ").AddYellow(customizationSplit[0], true) + .AddText(" could not be identified as a valid type.").BuiltString); + return false; + } + + customizeIndex = CustomizationExtensions.AllBasic[customizeInt]; + } + + var valueString = customizationSplit[1].ToLowerInvariant(); + var (wrapAround, offset) = valueString switch + { + "next" => (true, (sbyte)1), + "previous" => (true, (sbyte)-1), + "plus" => (false, (sbyte)1), + "minus" => (false, (sbyte)-1), + _ => (false, (sbyte)0), + }; + byte? baseValue = null; + if (offset == 0) + { + if (byte.TryParse(valueString, out var b)) + { + baseValue = b; + } + else + { + _chat.Print(new SeStringBuilder().AddText("The customization value ").AddPurple(valueString, true) + .AddText(" could not be parsed.") + .BuiltString); + return false; + } + } + + if (customizationSplit.Length < 3 || !byte.TryParse(customizationSplit[2], out var multiplier)) + multiplier = 1; + + 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)) + { + if (_stateManager.TryGetValue(identifier, out var state)) + ApplyToState(state); + } + else + { + foreach (var actor in actors.Objects) + { + if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) + ApplyToState(state); + } + } + } + + return true; + + void ApplyToState(ActorState state) + { + var customize = state.ModelData.Customize; + if (!state.ModelData.IsHuman) + return; + + var set = _customizeService.Manager.GetSet(customize.Clan, customize.Gender); + if (!set.IsAvailable(customizeIndex)) + return; + + if (baseValue != null) + { + var v = baseValue.Value; + if (set.Type(customizeIndex) is CharaMakeParams.MenuType.ListSelector) + --v; + set.DataByValue(customizeIndex, new CustomizeValue(v), out var data, customize.Face); + if (data != null) + _stateManager.ChangeCustomize(state, customizeIndex, data.Value.Value, ApplySettings.Manual); + } + else + { + var idx = set.DataByValue(customizeIndex, customize[customizeIndex], out var data, customize.Face); + var count = set.Count(customizeIndex, customize.Face); + var m = multiplier % count; + var newIdx = offset is 1 + ? idx >= count - m + ? wrapAround + ? m + idx - count + : count - 1 + : idx + m + : idx < m + ? wrapAround + ? count - m + idx + : 0 + : idx - m; + data = set.Data(customizeIndex, newIdx, customize.Face); + _stateManager.ChangeCustomize(state, customizeIndex, data.Value.Value, ApplySettings.Manual); + } + } + + bool PrintCustomizationHelp() + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour applycustomization ").AddYellow("[Customization Type]") + .AddPurple(" [Value, Next, Previous, Minus, or Plus] ") + .AddBlue("") + .AddText(" | ") + .AddGreen("[Character Identifier]") + .BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》 Valid ").AddPurple("values") + .AddText(" depend on the the character's gender, clan, and the customization type.").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》 ").AddPurple("Plus").AddText(" and ").AddPurple("Minus") + .AddText(" are the same as pressing the + and - buttons in the UI, times the optional ").AddBlue(" amount").AddText(".") + .BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》 ").AddPurple("Next").AddText(" and ").AddPurple("Previous") + .AddText(" is similar to Plus and Minus, but with wrap-around on reaching the end.").BuiltString); + var builder = new SeStringBuilder().AddText(" 》 Available ").AddYellow("Customization Types") + .AddText(" are either a number in ") + .AddYellow($"[0, {CustomizationExtensions.AllBasic.Length}]") + .AddText(" or one of "); + foreach (var index in CustomizationExtensions.AllBasic.SkipLast(1)) + builder.AddYellow(index.ToString()).AddText(", "); + _chat.Print(builder.AddYellow(CustomizationExtensions.AllBasic[^1].ToString()).AddText(".").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 The item name is case-insensitive. Numeric IDs are preferred before item names.") + .BuiltString); + PlayerIdentifierHelp(false, true); + return true; + } + } + private bool Apply(string arguments) { var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); From 2713e6f1f61b3b4196ba41e7e94e54bae076166c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 5 May 2024 15:29:37 +0200 Subject: [PATCH 29/45] Add an option for designs to always force a redraw. --- Glamourer/Designs/Design.cs | 5 ++++- Glamourer/Designs/DesignManager.cs | 12 ++++++++++++ Glamourer/Designs/IDesignStandIn.cs | 2 ++ Glamourer/Designs/Links/DesignMerger.cs | 11 ++++++++--- Glamourer/Designs/Links/MergedDesign.cs | 3 +++ Glamourer/Designs/Special/QuickSelectedDesign.cs | 3 +++ Glamourer/Designs/Special/RandomDesign.cs | 3 +++ Glamourer/Designs/Special/RevertDesign.cs | 3 +++ Glamourer/Events/DesignChanged.cs | 3 +++ Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 7 +++++++ Glamourer/State/StateEditor.cs | 6 ++++-- 11 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 14a3c0e..f09e7bc 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -43,6 +43,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } + public bool ForcedRedraw { get; internal set; } public bool QuickDesign { get; internal set; } = true; public string Color { get; internal set; } = string.Empty; public SortedList AssociatedMods { get; private set; } = []; @@ -99,6 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["LastEdit"] = LastEdit, ["Name"] = Name.Text, ["Description"] = Description, + ["ForcedRedraw"] = ForcedRedraw, ["Color"] = Color, ["QuickDesign"] = QuickDesign, ["Tags"] = JArray.FromObject(Tags), @@ -173,7 +175,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn LoadParameters(json["Parameters"], design, design.Name); LoadMaterials(json["Materials"], design, design.Name); LoadLinks(linkLoader, json["Links"], design); - design.Color = json["Color"]?.ToObject() ?? string.Empty; + design.Color = json["Color"]?.ToObject() ?? string.Empty; + design.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; return design; static string[] ParseTags(JObject json) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 03f0590..7bd949c 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -193,6 +193,7 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); } + /// Change the associated color of a design. public void ChangeColor(Design design, string newColor) { var oldColor = design.Color; @@ -311,6 +312,17 @@ public sealed class DesignManager : DesignEditor #region Edit Application Rules + public void ChangeForcedRedraw(Design design, bool forcedRedraw) + { + if (design.ForcedRedraw == forcedRedraw) + return; + + design.ForcedRedraw = forcedRedraw; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? "not" : string.Empty)} force redraws."); + DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); + } + /// Change whether to apply a specific customize value. public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) { diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs index 4865068..492bc6b 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -23,4 +23,6 @@ public interface IDesignStandIn : IEquatable public void ParseData(JObject jObj); public bool ChangeData(object data); + + public bool ForcedRedraw { get; } } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 44f4db9..558377a 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -19,9 +19,11 @@ public class DesignMerger( { public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations) - => Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type, JobFlag.All)), currentCustomize, baseRef, respectOwnership, modAssociations); + => Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type, JobFlag.All)), currentCustomize, baseRef, respectOwnership, + modAssociations); - public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, + public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize, + in DesignData baseRef, bool respectOwnership, bool modAssociations) { var ret = new MergedDesign(designManager); @@ -51,6 +53,8 @@ public class DesignMerger( ReduceMods(design as Design, ret, modAssociations); if (type.HasFlag(ApplicationType.GearCustomization)) ReduceMaterials(design, ret); + if (design.ForcedRedraw) + ret.ForcedRedraw = true; } ApplyFixFlags(ret, fixFlags); @@ -189,7 +193,8 @@ public class DesignMerger( ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs); } - private void ReduceOffhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) + private void ReduceOffhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, + bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Offhand)) return; diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 131b074..d3c7664 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -88,6 +88,8 @@ public sealed class MergedDesign if (weapon.Valid) Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All); } + + ForcedRedraw = design is IDesignStandIn { ForcedRedraw: true }; } public MergedDesign(Design design) @@ -101,4 +103,5 @@ public sealed class MergedDesign public readonly WeaponList Weapons = new(); public readonly SortedList AssociatedMods = []; public StateSources Sources = new(); + public bool ForcedRedraw; } diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index f347085..c506f0a 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -50,4 +50,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public bool ChangeData(object data) => false; + + public bool ForcedRedraw + => combo.Design?.ForcedRedraw ?? false; } diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index c09fd2b..5fac61b 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -78,4 +78,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn Predicates = predicates; return true; } + + public bool ForcedRedraw + => false; } diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index e450ff1..023d5eb 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -42,4 +42,7 @@ public class RevertDesign : IDesignStandIn public bool ChangeData(object data) => false; + + public bool ForcedRedraw + => false; } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index cd51f6d..1837aad 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -80,6 +80,9 @@ public sealed class DesignChanged() /// An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. MaterialRevert, + /// An existing design had changed whether it always forces a redraw or not. + ForceRedraw, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index e56ec00..ecac046 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -136,6 +136,13 @@ public class DesignDetailTab if (hovered || ImGui.IsItemHovered()) ImGui.SetTooltip("Display or hide this design in your quick design bar."); + var forceRedraw = _selector.Selected!.ForcedRedraw; + ImGuiUtil.DrawFrameColumn("Force Redrawing"); + ImGui.TableNextColumn(); + if (ImGui.Checkbox("##ForceRedraw", ref forceRedraw)) + _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); + ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); + ImGuiUtil.DrawFrameColumn("Color"); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; ImGui.TableNextColumn(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 30a61ce..bd5d1e0 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -212,7 +212,9 @@ public class StateEditor( mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; - var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman; + var requiresRedraw = mergedDesign.ForcedRedraw + || oldModelId != mergedDesign.Design.DesignData.ModelId + || !mergedDesign.Design.DesignData.IsHuman; if (state.ModelData.IsHuman) { @@ -402,6 +404,6 @@ public class StateEditor( if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets)) ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands), - stain, settings); + stain, settings); } } From 6efd89e0abab2205d8bb1039bbb5adaa92a95af4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 5 May 2024 15:40:04 +0200 Subject: [PATCH 30/45] Make this option work when applying automation. --- Glamourer/Api/StateApi.cs | 4 +-- Glamourer/Automation/AutoDesignApplier.cs | 27 ++++++++++--------- Glamourer/Gui/DesignQuickBar.cs | 8 +++--- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 10 +++---- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 4 +-- Glamourer/Services/CommandService.cs | 6 ++--- Glamourer/State/StateManager.cs | 8 +++--- 7 files changed, 35 insertions(+), 32 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 3e2fde4..344fc5c 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -293,8 +293,8 @@ 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); - _stateManager.ReapplyState(actor, state, source); + _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, out var forcedRedraw); + _stateManager.ReapplyState(actor, state, forcedRedraw, source); ApiHelpers.Lock(state, key, flags); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index c739a75..f2ac1b3 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -152,9 +152,9 @@ public sealed class AutoDesignApplier : IDisposable { if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false); + Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw); foreach (var actor in data.Objects) - _state.ReapplyState(actor, StateSource.Fixed); + _state.ReapplyState(actor, forcedRedraw,StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -164,8 +164,8 @@ public sealed class AutoDesignApplier : IDisposable var specificId = actor.GetIdentifier(_actors); if (_state.GetOrCreate(specificId, actor, out var state)) { - Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false); - _state.ReapplyState(actor, StateSource.Fixed); + Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw); + _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } } } @@ -212,12 +212,13 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = state.LastJob == newJob.Id; state.LastJob = actor.Job; - Reduce(actor, state, set, respectManual, true); - _state.ReapplyState(actor, StateSource.Fixed); + Reduce(actor, state, set, respectManual, true, out var forcedRedraw); + _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } - public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset) + public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, out bool forcedRedraw) { + forcedRedraw = false; if (!_config.EnableAutoDesigns) return; @@ -226,7 +227,7 @@ public sealed class AutoDesignApplier : IDisposable if (reset) _state.ResetState(state, StateSource.Game); - Reduce(actor, state, set, false, false); + Reduce(actor, state, set, false, false, out forcedRedraw); } public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) @@ -253,11 +254,11 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange; if (!respectManual) _state.ResetState(state, StateSource.Game); - Reduce(actor, state, set, respectManual, false); + Reduce(actor, state, set, respectManual, false, out _); return true; } - private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange) + private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, out bool forcedRedraw) { if (set.BaseState is AutoDesignSet.Base.Game) { @@ -275,6 +276,7 @@ public sealed class AutoDesignApplier : IDisposable } } + forcedRedraw = false; if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; @@ -282,6 +284,7 @@ public sealed class AutoDesignApplier : IDisposable set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); + forcedRedraw = mergedDesign.ForcedRedraw; } /// Get world-specific first and all-world afterward. @@ -323,10 +326,10 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = prior == id; NewGearsetId = id; - Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob); + Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, out var forcedRedraw); NewGearsetId = -1; foreach (var actor in data.Objects) - _state.ReapplyState(actor, StateSource.Fixed); + _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } public static unsafe bool CheckGearset(short check) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 50e976f..d81b1ae 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -251,8 +251,8 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, true); - _stateManager.ReapplyState(actor, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw); + _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); } } @@ -291,8 +291,8 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, false); - _stateManager.ReapplyState(actor, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw); + _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index b8ecd53..5a05058 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -341,8 +341,8 @@ 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); - _stateManager.ReapplyState(_actor, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw); + _stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual); } ImGui.SameLine(); @@ -350,15 +350,15 @@ 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); - _stateManager.ReapplyState(_actor, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw); + _stateManager.ReapplyState(_actor, forcedRedraw, 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, StateSource.Manual); + _stateManager.ReapplyState(_actor, false, StateSource.Manual); } private void DrawApplyToSelf() diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 11f7fd9..9359bea 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -86,7 +86,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _actions.Enqueue((state, () => { foreach (var actor in actors.Objects) - _state.ReapplyState(actor, state, StateSource.IpcManual); + _state.ReapplyState(actor, state, false, StateSource.IpcManual); Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); }, WaitFrames)); } @@ -106,7 +106,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _frame = currentFrame; _framework.RunOnFrameworkThread(() => { - _state.ReapplyState(_objects.Player, StateSource.IpcManual); + _state.ReapplyState(_objects.Player, false, StateSource.IpcManual); Glamourer.Log.Debug( $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); }); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 47ffa00..b18c817 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -329,8 +329,8 @@ public class CommandService : IDisposable, IApiService { if (_stateManager.GetOrCreate(identifier, actor, out var state)) { - _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert); - _stateManager.ReapplyState(actor, StateSource.Manual); + _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw); + _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); } } } @@ -379,7 +379,7 @@ public class CommandService : IDisposable, IApiService return true; foreach (var actor in data.Objects) - _stateManager.ReapplyState(actor, StateSource.Manual); + _stateManager.ReapplyState(actor, false, StateSource.Manual); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 7fe2264..77e8797 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -402,18 +402,18 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, StateSource source) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, source); + ReapplyState(actor, state, forceRedraw, source); } - public void ReapplyState(Actor actor, ActorState state, StateSource source) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) { var data = Applier.ApplyAll(state, - !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); } From 91138176e0fbcd7b3defdd8a8be72c292196600b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 May 2024 18:13:41 +0200 Subject: [PATCH 31/45] Change API Version. --- Glamourer/Api/GlamourerApi.cs | 4 ++-- Glamourer/Gui/DesignQuickBar.cs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index e08537a..2c555af 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -5,8 +5,8 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { - public const int CurrentApiVersionMajor = 2; - public const int CurrentApiVersionMinor = 0; + public const int CurrentApiVersionMajor = 1; + public const int CurrentApiVersionMinor = 1; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d81b1ae..a0a341a 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -100,7 +100,6 @@ public sealed class DesignQuickBar : Window, IDisposable private void Draw(float width) { - _objects.Update(); using var group = ImRaii.Group(); var spacing = ImGui.GetStyle().ItemInnerSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -113,7 +112,7 @@ public sealed class DesignQuickBar : Window, IDisposable ImGui.SameLine(); DrawApplyButton(buttonSize); } - + DrawRevertButton(buttonSize); DrawRevertEquipButton(buttonSize); DrawRevertCustomizeButton(buttonSize); @@ -132,7 +131,6 @@ public sealed class DesignQuickBar : Window, IDisposable private void PrepareButtons() { - _objects.Update(); (_playerIdentifier, _playerData) = _objects.PlayerData; (_targetIdentifier, _targetData) = _objects.TargetData; _playerState = _stateManager.GetValueOrDefault(_playerIdentifier); From e4883b15cc0c5383ab107c875604532e7159de42 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 May 2024 23:40:09 +0200 Subject: [PATCH 32/45] Add a overwrite with player state button to design tab. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 37 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f00a74c..e0d2da7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -17,6 +17,7 @@ using OtterGui.Raii; using Penumbra.GameData.Enums; using System; using static Glamourer.Gui.Tabs.HeaderDrawer; +using static System.Net.Mime.MediaTypeNames; namespace Glamourer.Gui.Tabs.DesignTab; @@ -38,8 +39,8 @@ public class DesignPanel private readonly CustomizeParameterDrawer _parameterDrawer; private readonly DesignLinkDrawer _designLinkDrawer; private readonly MaterialDrawer _materials; - private readonly Button[] _leftButtons; - private readonly Button[] _rightButtons; + private readonly Button[] _leftButtons; + private readonly Button[] _rightButtons; public DesignPanel(DesignFileSystemSelector selector, @@ -78,6 +79,7 @@ public class DesignPanel new SetFromClipboardButton(this), new UndoButton(this), new ExportToClipboardButton(this), + new ApplyCharacterButton(this), ]; _rightButtons = [ @@ -561,4 +563,35 @@ public class DesignPanel } } } + + private sealed class ApplyCharacterButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selector.Selected != null && panel._objects.Player.Valid; + + protected override string Description + => "Overwrite this design with your character's current state."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.UserEdit; + + protected override void OnClick() + { + try + { + var (player, actor) = panel._objects.PlayerData; + if (!player.IsValid || !actor.Valid || !panel._state.GetOrCreate(player, actor.Objects[0], out var state)) + throw new Exception("No player state available."); + + var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._manager.ApplyDesign(panel._selector.Selected!, design); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply player state to {panel._selector.Selected!.Name}.", + $"Could not apply player state to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false); + } + } + } } From dbd11f6a95cc21ba600b52e5cc5d7dc0213653cf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 May 2024 15:28:53 +0200 Subject: [PATCH 33/45] Add initial changelog and preliminary data for existing cheatcodes, no hints yet. --- Glamourer/Gui/GlamourerChangelog.cs | 22 +++++++++++++++++ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 -- Glamourer/Services/CodeService.cs | 26 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 0e4b934..b91d642 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -32,6 +32,7 @@ public class GlamourerChangelog AddDummy(Changelog); Add1_2_0_0(Changelog); Add1_2_1_0(Changelog); + Add1_2_2_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -52,6 +53,27 @@ public class GlamourerChangelog } } + private static void Add1_2_2_0(Changelog log) + => log.NextVersion("Version 1.2.2.0") + .RegisterHighlight("Added a field to rename designs directly from the mod selector context menu, instead of moving them in the filesystem.") + .RegisterEntry("You can choose which rename field (none, either one or both) to display in the settings.", 1) + .RegisterEntry("Automatically applied offhand weapons due to mainhand settings now also apply the mainhands dye.") + .RegisterHighlight("Added a height display in real-world units next to the height-selector.") + .RegisterEntry("This can be configured to use your favourite unit of measurement, even wrong ones, or not display at all.", 1) + .RegisterHighlight("Added a chat command '/glamour applycustomization' that can apply single customization values to actors. Use without arguments for help.") + .RegisterHighlight("Added an option for designs to always force a redraw when applied to a character, regardless of whether it is necessary or not.") + .RegisterHighlight("Added a button to overwrite the selected design with the current player state.") + .RegisterEntry("Added some copy/paste functionality for mod associations.") + .RegisterEntry("Reworked the API and IPC structure heavily.") + .RegisterEntry("Fixed weapon selectors not having a favourite star available.") + .RegisterEntry("Fixed issues with items with custom names.") + .RegisterEntry("Fixed the labels for eye colors.") + .RegisterEntry("Fixed the tooltip for Apply Dye checkboxes.") + .RegisterEntry("Fixed an issue when hovering over assigned mod settings.") + .RegisterEntry("Made conformant to Dalamud guidelines by adding a button to open the main UI.") + .RegisterEntry("Fixed an issue with visor states. (1.2.1.3)") + .RegisterEntry("Fixed an issue with identical weapon types and multiple restricted designs. (1.2.1.3)"); + private static void Add1_2_1_0(Changelog log) => log.NextVersion("Version 1.2.1.0") .RegisterEntry("Updated for .net 8 and FFXIV 6.58, using some new framework options to improve performance and stability.") diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index e0d2da7..bf9ba69 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -15,9 +15,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; -using System; using static Glamourer.Gui.Tabs.HeaderDrawer; -using static System.Net.Mime.MediaTypeNames; namespace Glamourer.Gui.Tabs.DesignTab; diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 71f1a45..fb7a55e 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -205,4 +205,30 @@ public class CodeService CodeFlag.Dolphins => [ 0x64, 0xC6, 0x2E, 0x7C, 0x22, 0x3A, 0x42, 0xF5, 0xC3, 0x93, 0x4F, 0x70, 0x1F, 0xFD, 0xFA, 0x3C, 0x98, 0xD2, 0x7C, 0xD8, 0x88, 0xA7, 0x3D, 0x1D, 0x0D, 0xD6, 0x70, 0x15, 0x28, 0x2E, 0x79, 0xE7 ], _ => [], }; + + private static (int CapitalCount, string Punctuation, string Hint, string Effect) GetData(CodeFlag flag) + => flag switch + { + CodeFlag.Clown => (2, ",.", "", "Randomizes dyes for every player."), + CodeFlag.Emperor => (1, ".", "", "Randomizes clothing for every player."), + CodeFlag.Individual => (2, "'!'!", "", "Randomizes customizations for every player."), + CodeFlag.Dwarf => (1, "!", "", "Sets the player character to minimum height and all other players to maximum height."), + CodeFlag.Giant => (2, "!", "", "Sets the player character to maximum height and all other players to minimum height."), + CodeFlag.OopsHyur => (1, "','.", "", "Turns all players to Hyur."), + CodeFlag.OopsElezen => (1, ".", "", "Turns all players to Elezen."), + CodeFlag.OopsLalafell => (2, ",!", "", "Turns all players to Lalafell."), + CodeFlag.OopsMiqote => (3, ".", "", "Turns all players to Miqo'te."), + CodeFlag.OopsRoegadyn => (1, "!", "", "Turns all players to Roegadyn."), + CodeFlag.OopsAuRa => (1, "',.", "", "Turns all players to Au Ra."), + CodeFlag.OopsHrothgar => (1, "',...", "", "Turns all players to Hrothgar."), + CodeFlag.OopsViera => (2, "!'!", "", "Turns all players to Viera."), + CodeFlag.Artisan => (3, ",,!", "", "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.SixtyThree => (2, "", "", "Inverts the gender of every player."), + CodeFlag.Shirts => (1, "-.", "", "Highlights all items in the Unlocks tab as if they were unlocked."), + CodeFlag.World => (1, ",.", "", "Sets every player except the player character themselves to job-appropriate gear."), + CodeFlag.Elephants => (1, "!", "", "Sets every player to the elephant costume in varying shades of pink."), + CodeFlag.Crown => (1, ".", "", "Sets every player with a mentor symbol enabled to the clown's hat."), + CodeFlag.Dolphins => (5, ",", "", "Sets every player to a Namazu hat with different costume bodies."), + _ => (0, string.Empty, string.Empty, string.Empty), + }; } From 93dcd317c19eb17a5a8ba74c49cdb82567afe8f0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 May 2024 15:43:19 +0200 Subject: [PATCH 34/45] Update Submodules --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index 3460a81..1d93651 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3460a817fc5e01a6b60eb834c3c59031938388fc +Subproject commit 1d9365164655a7cb38172e1311e15e19b1def6db diff --git a/Penumbra.Api b/Penumbra.Api index 590629d..69d106b 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 590629df33f9ad92baddd1d65ec8c986f18d608a +Subproject commit 69d106b457eb0f73d4b4caf1234da5631fd6fbf0 diff --git a/Penumbra.GameData b/Penumbra.GameData index 2a34969..07cc26f 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2a349691de9ae9bd10f6d3a247a1227ddfea97b3 +Subproject commit 07cc26f196984a44711b3bc4c412947d863288bd From 1341c4316c3dcf358740022d5a1ee5683b99563b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 27 May 2024 17:42:49 +0200 Subject: [PATCH 35/45] Use Legacy Color Tables because our materials do not have DT ones yet. --- Glamourer/Designs/DesignConverter.cs | 4 ++-- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 12 +++++----- Glamourer/Gui/Materials/ColorRowClipboard.cs | 8 +++---- Glamourer/Gui/Materials/MaterialDrawer.cs | 6 ++--- Glamourer/Interop/Material/DirectXService.cs | 22 +++++++++---------- .../Material/LiveColorTablePreviewer.cs | 18 +++++++-------- Glamourer/Interop/Material/MaterialManager.cs | 4 ++-- Glamourer/Interop/Material/MaterialService.cs | 12 +++++----- .../Interop/Material/MaterialValueIndex.cs | 4 ++-- .../Interop/Material/MaterialValueManager.cs | 6 ++--- Glamourer/Interop/Material/PrepareColorSet.cs | 11 +++++----- Penumbra.GameData | 2 +- 12 files changed, 55 insertions(+), 54 deletions(-) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index a7358b8..f3955c5 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -7,7 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; namespace Glamourer.Designs; @@ -224,7 +224,7 @@ public class DesignConverter( foreach (var (key, value) in materials.Values) { var idx = MaterialValueIndex.FromKey(key); - if (idx.RowIndex >= MtrlFile.ColorTable.NumRows) + if (idx.RowIndex >= LegacyColorTable.NumUsedRows) continue; if (idx.MaterialIndex >= MaterialService.MaterialsPerModel) continue; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index c2dbdbe..232541e 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -11,7 +11,7 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Penumbra.String; @@ -190,11 +190,11 @@ public sealed unsafe class AdvancedDyePopup( DrawWindow(textures); } - private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) + private void DrawTable(MaterialValueIndex materialIndex, in LegacyColorTable table) { using var disabled = ImRaii.Disabled(_state.IsLocked); _anyChanged = false; - for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i) { var index = materialIndex with { RowIndex = i }; ref var row = ref table[i]; @@ -205,7 +205,7 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } - private void DrawAllRow(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) + private void DrawAllRow(MaterialValueIndex materialIndex, in LegacyColorTable table) { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -242,11 +242,11 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SameLine(0, spacing); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged, true)) - for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } - private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table) + private void DrawRow(ref LegacyColorTable.Row row, MaterialValueIndex index, in LegacyColorTable table) { using var id = ImRaii.PushId(index.RowIndex); var changed = _state.Materials.TryGetValue(index, out var value); diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs index f8310c3..4d99018 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -1,18 +1,18 @@ using Glamourer.Interop.Material; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; namespace Glamourer.Gui.Materials; public static class ColorRowClipboard { - private static ColorRow _row; - private static MtrlFile.ColorTable _table; + private static ColorRow _row; + private static LegacyColorTable _table; public static bool IsSet { get; private set; } public static bool IsTableSet { get; private set; } - public static MtrlFile.ColorTable Table + public static LegacyColorTable Table { get => _table; set diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index aeb4a9b..26432e9 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -7,7 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Services; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Gui; namespace Glamourer.Gui.Materials; @@ -175,9 +175,9 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) { _newRowIdx += 1; ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X); - if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, MtrlFile.ColorTable.NumRows, "Row #%i")) + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, LegacyColorTable.NumUsedRows, "Row #%i")) { - _newRowIdx = Math.Clamp(_newRowIdx, 1, MtrlFile.ColorTable.NumRows); + _newRowIdx = Math.Clamp(_newRowIdx, 1, LegacyColorTable.NumUsedRows); _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; } diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index d3fa3db..6d9c71b 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -2,11 +2,11 @@ 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 static Penumbra.GameData.Files.MtrlFile; using MapFlags = Vortice.Direct3D11.MapFlags; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; @@ -14,14 +14,14 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { - private readonly object _lock = new(); - private readonly ConcurrentDictionary _textures = []; + 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. /// The original texture that will be replaced with a new one. /// The input color table. /// Success or failure. - public bool ReplaceColorTable(Texture** original, in ColorTable colorTable) + public bool ReplaceColorTable(Texture** original, in LegacyColorTable colorTable) { if (original == null) return false; @@ -38,7 +38,7 @@ public unsafe class DirectXService(IFramework framework) : IService if (texture.IsInvalid) return false; - fixed (ColorTable* ptr = &colorTable) + fixed (LegacyColorTable* ptr = &colorTable) { if (!texture.Texture->InitializeContents(ptr)) return false; @@ -51,7 +51,7 @@ public unsafe class DirectXService(IFramework framework) : IService return true; } - public bool TryGetColorTable(Texture* texture, out ColorTable table) + public bool TryGetColorTable(Texture* texture, out LegacyColorTable table) { if (_textures.TryGetValue((nint)texture, out var p) && framework.LastUpdateUTC == p.Update) { @@ -73,7 +73,7 @@ public unsafe class DirectXService(IFramework framework) : IService /// A pointer to the internal texture struct containing the GPU handle. /// The returned color table. /// Whether the table could be fetched. - private static bool TextureColorTable(Texture* texture, out ColorTable table) + private static bool TextureColorTable(Texture* texture, out LegacyColorTable table) { if (texture == null) { @@ -114,7 +114,7 @@ public unsafe class DirectXService(IFramework framework) : IService } /// Turn a mapped texture into a color table. - private static ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) + private static LegacyColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) { var desc = resource.Description1; @@ -133,14 +133,14 @@ public unsafe class DirectXService(IFramework framework) : IService /// The height of the texture. (Needs to be 16). /// The stride in the texture data. /// - private static ColorTable ReadTexture(nint data, int length, int height, int pitch) + private static LegacyColorTable ReadTexture(nint data, int length, int height, int pitch) { // Check that the data has sufficient dimension and size. var expectedSize = sizeof(Half) * MaterialService.TextureWidth * height * 4; - if (length < expectedSize || sizeof(ColorTable) != expectedSize || height != MaterialService.TextureHeight) + if (length < expectedSize || sizeof(LegacyColorTable) != expectedSize || height != MaterialService.TextureHeight) return default; - var ret = new ColorTable(); + var ret = new LegacyColorTable(); var target = (byte*)&ret; // If the stride is the same as in the table, just copy. if (pitch == MaterialService.TextureWidth) diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index e732472..aa4c358 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using ImGuiNET; using OtterGui.Services; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; @@ -12,12 +12,12 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private readonly IFramework _framework; private readonly DirectXService _directXService; - public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; - public MtrlFile.ColorTable LastOriginalColorTable { get; private set; } - private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; - private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; - private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; - private MtrlFile.ColorTable _originalColorTable; + public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; + public LegacyColorTable LastOriginalColorTable { get; private set; } + private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; + private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; + private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; + private LegacyColorTable _originalColorTable; public LiveColorTablePreviewer(global::Penumbra.GameData.Interop.ObjectManager objects, IFramework framework, DirectXService directXService) { @@ -78,7 +78,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable } else { - for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + for (var i = 0; i < LegacyColorTable.NumUsedRows; ++i) { table[i].Diffuse = diffuse; table[i].Emissive = emissive; @@ -92,7 +92,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable _objectIndex = ObjectIndex.AnyIndex; } - public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table) + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, LegacyColorTable table) { if (_valueIndex.DrawObject is not MaterialValueIndex.DrawObjectType.Invalid) return; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 1fb758b..8e3936e 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -6,7 +6,7 @@ using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; @@ -76,7 +76,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Update and apply the glamourer state of an actor according to the application sources when updated by the game. private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData, - ref MtrlFile.ColorTable colorTable) + ref LegacyColorTable colorTable) { var deleteList = _deleteList.Value!; deleteList.Clear(); diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 03165a3..4c8706c 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -1,8 +1,8 @@ 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 static Penumbra.GameData.Files.MtrlFile; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; namespace Glamourer.Interop.Material; @@ -10,10 +10,10 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { public const int TextureWidth = 4; - public const int TextureHeight = ColorTable.NumRows; + public const int TextureHeight = LegacyColorTable.NumUsedRows; public const int MaterialsPerModel = 4; - public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) + public static bool GenerateNewColorTable(in LegacyColorTable colorTable, out Texture* texture) { var textureSize = stackalloc int[2]; textureSize[0] = TextureWidth; @@ -24,7 +24,7 @@ public static unsafe class MaterialService if (texture == null) return false; - fixed (ColorTable* ptr = &colorTable) + fixed (LegacyColorTable* ptr = &colorTable) { return texture->InitializeContents(ptr); } @@ -53,7 +53,7 @@ public static unsafe class MaterialService /// The model slot. /// The material slot in the model. /// A pointer to the color table or null. - public static ColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot) + public static LegacyColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot) { if (!model.IsCharacterBase) return null; @@ -66,6 +66,6 @@ public static unsafe class MaterialService if (material == null || material->ColorTable == null) return null; - return (ColorTable*)material->ColorTable; + return (LegacyColorTable*)material->ColorTable; } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index ec9996b..9bfcc4c 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -2,7 +2,7 @@ using FFXIVClientStructs.Interop; using Newtonsoft.Json; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; namespace Glamourer.Interop.Material; @@ -143,7 +143,7 @@ public readonly record struct MaterialValueIndex( => materialIndex < MaterialService.MaterialsPerModel; public static bool ValidateRow(byte rowIndex) - => rowIndex < MtrlFile.ColorTable.NumRows; + => rowIndex < LegacyColorTable.NumUsedRows; private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex) { diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 599d264..483e6af 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -2,9 +2,9 @@ global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager; using Glamourer.GameData; using Glamourer.State; -using Penumbra.GameData.Files; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; @@ -21,7 +21,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float SpecularStrength = specularStrength; public float GlossStrength = glossStrength; - public ColorRow(in MtrlFile.ColorTable.Row row) + public ColorRow(in LegacyColorTable.Row row) : this(Root(row.Diffuse), Root(row.Specular), Root(row.Emissive), row.SpecularStrength, row.GlossStrength) { } @@ -44,7 +44,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa private static float Root(float value) => value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value); - public readonly bool Apply(ref MtrlFile.ColorTable.Row row) + public readonly bool Apply(ref LegacyColorTable.Row row) { var ret = false; var d = Square(Diffuse); diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 5257c4e..1661037 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; @@ -55,7 +55,7 @@ public sealed unsafe class PrepareColorSet } public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, - out MtrlFile.ColorTable table) + out LegacyColorTable table) { if (material->ColorTable == null) { @@ -63,7 +63,7 @@ public sealed unsafe class PrepareColorSet return false; } - var newTable = *(MtrlFile.ColorTable*)material->ColorTable; + var newTable = *(LegacyColorTable*)material->ColorTable; if (stainId.Id != 0) characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); table = newTable; @@ -71,11 +71,12 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out MtrlFile.ColorTable table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out LegacyColorTable table) { - var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; + var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) return false; + var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; if (handle == null) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 07cc26f..b828297 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 07cc26f196984a44711b3bc4c412947d863288bd +Subproject commit b8282970ee78a2c085e740f60450fecf7ea58b9c From 13181311ae6c511420bace13b487d17a14c768f1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 27 May 2024 15:45:54 +0000 Subject: [PATCH 36/45] [CI] Updating repo.json for testing_1.2.2.0 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index d2cd3ef..3f0efad 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.1.3", + "TestingAssemblyVersion": "1.2.2.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.2.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 284920b8fee05423c38c14e17e00605ea1fc80ba Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 28 May 2024 18:06:49 +0200 Subject: [PATCH 37/45] Add check for Penumbra API mismatch. --- Glamourer/Gui/MainWindow.cs | 71 ++++++++++++++++--- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 8 +++ Glamourer/Interop/Penumbra/PenumbraService.cs | 22 ++++-- 3 files changed, 86 insertions(+), 15 deletions(-) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 4de03c5..f7cd589 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,5 +1,4 @@ -using Dalamud.Interface.Utility; -using Dalamud.Interface.Windowing; +using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.Events; @@ -11,9 +10,13 @@ using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; +using Glamourer.Interop.Penumbra; using ImGuiNET; +using OtterGui; using OtterGui.Custom; +using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui; @@ -41,10 +44,12 @@ public class MainWindow : Window, IDisposable } private readonly Configuration _config; + private readonly PenumbraService _penumbra; private readonly DesignQuickBar _quickBar; private readonly TabSelected _event; private readonly MainWindowPosition _position; private readonly ITab[] _tabs; + private bool _ignorePenumbra = false; public readonly SettingsTab Settings; public readonly ActorTab Actors; @@ -59,7 +64,7 @@ public class MainWindow : Window, IDisposable public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar, - NpcTab npcs, MainWindowPosition position) + NpcTab npcs, MainWindowPosition position, PenumbraService penumbra) : base("GlamourerMainWindow") { pi.UiBuilder.DisableGposeUiHide = true; @@ -80,6 +85,7 @@ public class MainWindow : Window, IDisposable Npcs = npcs; _position = position; _config = config; + _penumbra = penumbra; _tabs = [ settings, @@ -119,18 +125,34 @@ public class MainWindow : Window, IDisposable var yPos = ImGui.GetCursorPosY(); _position.Size = ImGui.GetWindowSize(); _position.Position = ImGui.GetWindowPos(); - if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs)) - SelectTab = TabType.None; - var tab = FromLabel(currentTab); - if (tab != _config.Ephemeral.SelectedTab) + if (!_penumbra.Available && !_ignorePenumbra) { - _config.Ephemeral.SelectedTab = FromLabel(currentTab); - _config.Ephemeral.Save(); + if (_penumbra.CurrentMajor == 0) + DrawProblemWindow( + "Could not attach to Penumbra. Please make sure Penumbra is installed and running.\n\nPenumbra is required for Glamourer to work properly."); + else if (_penumbra is { CurrentMajor: PenumbraService.RequiredPenumbraBreakingVersion, CurrentMinor: >= PenumbraService.RequiredPenumbraFeatureVersion }) + DrawProblemWindow( + $"You are currently not attached to Penumbra, seemingly by manually detaching from it.\n\nPenumbra's last API Version was {_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}.\n\nPenumbra is required for Glamourer to work properly."); + else + DrawProblemWindow( + $"Attaching to Penumbra failed.\n\nPenumbra's API Version was {_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}, but Glamourer requires a version of {PenumbraService.RequiredPenumbraBreakingVersion}.{PenumbraService.RequiredPenumbraFeatureVersion}, where the major version has to match exactly, and the minor version has to be greater or equal.\nYou may need to update Penumbra or enable Testing Builds for it for this version of Glamourer.\n\nPenumbra is required for Glamourer to work properly."); } + else + { + if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs)) + SelectTab = TabType.None; + var tab = FromLabel(currentTab); - if (_config.ShowQuickBarInTabs) - _quickBar.DrawAtEnd(yPos); + if (tab != _config.Ephemeral.SelectedTab) + { + _config.Ephemeral.SelectedTab = FromLabel(currentTab); + _config.Ephemeral.Save(); + } + + if (_config.ShowQuickBarInTabs) + _quickBar.DrawAtEnd(yPos); + } } private ReadOnlySpan ToLabel(TabType type) @@ -192,4 +214,31 @@ public class MainWindow : Window, IDisposable (false, false) => $"Glamourer v{Glamourer.Version}###GlamourerMainWindow", (false, true) => $"Glamourer v{Glamourer.Version} (Incognito Mode)###GlamourerMainWindow", }; + + private void DrawProblemWindow(string text) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, Colors.SelectedRed); + ImGui.NewLine(); + ImGui.NewLine(); + ImGuiUtil.TextWrapped(text); + color.Pop(); + + ImGui.NewLine(); + if (ImUtf8.Button("Try Attaching Again"u8)) + _penumbra.Reattach(); + + var ignoreAllowed = _config.DeleteDesignModifier.IsActive(); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Ignore Penumbra This Time"u8, + $"Some functionality, like automation or retaining state, will not work correctly without Penumbra.\n\nIgnore this at your own risk!{(ignoreAllowed ? string.Empty : $"\n\nHold {_config.DeleteDesignModifier} while clicking to enable this button.")}", + default, !ignoreAllowed)) + _ignorePenumbra = true; + + ImGui.NewLine(); + ImGui.NewLine(); + CustomGui.DrawDiscordButton(Glamourer.Messager, 0); + ImGui.SameLine(); + ImGui.NewLine(); + ImGui.NewLine(); + } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 6b9a7a3..f1097e8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -37,6 +37,14 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem if (ImGui.SmallButton("Reattach")) _penumbra.Reattach(); + ImGuiUtil.DrawTableColumn("Version"); + ImGuiUtil.DrawTableColumn($"{_penumbra.CurrentMajor}.{_penumbra.CurrentMinor}"); + ImGui.TableNextColumn(); + + ImGuiUtil.DrawTableColumn("Attached When"); + ImGuiUtil.DrawTableColumn(_penumbra.AttachTime.ToLocalTime().ToLongTimeString()); + ImGui.TableNextColumn(); + ImGuiUtil.DrawTableColumn("Draw Object"); ImGui.TableNextColumn(); var address = _drawObject.Address; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 6d4c677..ad9207c 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -63,7 +63,10 @@ public unsafe class PenumbraService : IDisposable private readonly PenumbraReloaded _penumbraReloaded; - public bool Available { get; private set; } + public bool Available { get; private set; } + public int CurrentMajor { get; private set; } + public int CurrentMinor { get; private set; } + public DateTime AttachTime { get; private set; } public PenumbraService(DalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) { @@ -307,10 +310,21 @@ public unsafe class PenumbraService : IDisposable { Unattach(); - var (breaking, feature) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke(); - if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) + AttachTime = DateTime.UtcNow; + try + { + (CurrentMajor, CurrentMinor) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke(); + } + catch + { + CurrentMajor = 0; + CurrentMinor = 0; + throw; + } + + if (CurrentMajor != RequiredPenumbraBreakingVersion || CurrentMinor < RequiredPenumbraFeatureVersion) throw new Exception( - $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); + $"Invalid Version {CurrentMajor}.{CurrentMinor:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); _tooltipSubscriber.Enable(); _clickSubscriber.Enable(); From 20983a56058441494d90a6a2ea1cec4fedbb75b1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 29 May 2024 15:45:07 +0200 Subject: [PATCH 38/45] Fix reverting stains to game. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index b21f700..c5e5f7e 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -452,8 +452,8 @@ public class EquipmentDrawer else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) data.SetStain(Stain.None.RowIndex); - if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _)) - data.SetStain(Stain.None.RowIndex); + if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var newStain)) + data.SetStain(newStain); } private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) From 794cea72cc4c427fc447bec0f508e43187d9d4fc Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 30 May 2024 10:44:58 +0000 Subject: [PATCH 39/45] [CI] Updating repo.json for testing_1.2.2.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 3f0efad..6d998ad 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.2.0", + "TestingAssemblyVersion": "1.2.2.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.2.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.2.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 448090e8f5435d5925665bc417b976529109c510 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 May 2024 14:11:34 +0200 Subject: [PATCH 40/45] Better error message when not stupid. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index ad9207c..58422d7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -317,9 +317,16 @@ public unsafe class PenumbraService : IDisposable } catch { - CurrentMajor = 0; - CurrentMinor = 0; - throw; + try + { + (CurrentMajor, CurrentMinor) = new global::Penumbra.Api.IpcSubscribers.Legacy.ApiVersions(_pluginInterface).Invoke(); + } + catch + { + CurrentMajor = 0; + CurrentMinor = 0; + throw; + } } if (CurrentMajor != RequiredPenumbraBreakingVersion || CurrentMinor < RequiredPenumbraFeatureVersion) From 9c49b66d71085d40dc1756f74a731b7449a596a5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 30 May 2024 12:13:52 +0000 Subject: [PATCH 41/45] [CI] Updating repo.json for testing_1.2.2.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 6d998ad..20c490f 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.2.1", + "TestingAssemblyVersion": "1.2.2.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.8/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.2.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.2.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From edd55087dbce223bb7c5b0becae5c4062c36e561 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 May 2024 16:56:32 +0200 Subject: [PATCH 42/45] Add hints to codes. --- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 194 ++++++++++++++++++ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 74 +------ Glamourer/Services/CodeService.cs | 59 +++--- OtterGui | 2 +- 4 files changed, 228 insertions(+), 101 deletions(-) create mode 100644 Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs new file mode 100644 index 0000000..bf9e5cf --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -0,0 +1,194 @@ +using Dalamud.Interface; +using Glamourer.Services; +using Glamourer.State; +using ImGuiNET; +using OtterGui.Filesystem; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Text; +using OtterGui.Text.EndObjects; + +namespace Glamourer.Gui.Tabs.SettingsTab; + +public class CodeDrawer(Configuration config, CodeService codeService, FunModule funModule) : IUiService +{ + private static ReadOnlySpan Tooltip + => "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. "u8 + + "They allow for some fun easter-egg modes that usually manipulate the appearance of all players you see (including yourself) in some way."u8; + + private static ReadOnlySpan DragDropLabel + => "##CheatDrag"u8; + + private bool _showCodeHints; + private string _currentCode = string.Empty; + private int _dragCodeIdx = -1; + + + public void Draw() + { + var show = ImGui.CollapsingHeader("Cheat Codes"); + DrawTooltip(); + + if (!show) + return; + + DrawCodeInput(); + DrawCopyButtons(); + var knownFlags = DrawCodes(); + DrawCodeHints(knownFlags); + } + + private void DrawCodeInput() + { + var color = codeService.CheckCode(_currentCode).Item2 is not 0 ? ColorId.ActorAvailable : ColorId.ActorUnavailable; + using var border = ImRaii.PushFrameBorder(ImUtf8.GlobalScale, color.Value(), _currentCode.Length > 0); + ImGui.SetNextItemWidth(500 * ImUtf8.GlobalScale + ImUtf8.ItemSpacing.X); + if (ImUtf8.InputText("##Code"u8, ref _currentCode, "Enter Cheat Code..."u8, ImGuiInputTextFlags.EnterReturnsTrue)) + { + codeService.AddCode(_currentCode); + _currentCode = string.Empty; + } + + ImGui.SameLine(); + ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + DrawTooltip(); + } + + private void DrawCopyButtons() + { + var buttonSize = new Vector2(250 * ImUtf8.GlobalScale, 0); + if (ImUtf8.Button("Who am I?!?"u8, buttonSize)) + funModule.WhoAmI(); + ImUtf8.HoverTooltip( + "Copy your characters actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); + + ImGui.SameLine(); + + if (ImUtf8.Button("Who is that!?!"u8, buttonSize)) + funModule.WhoIsThat(); + ImUtf8.HoverTooltip( + "Copy your targets actual current appearance including cheat codes or holiday events to the clipboard as a design."u8); + } + + private CodeService.CodeFlag DrawCodes() + { + var canDelete = config.DeleteDesignModifier.IsActive(); + CodeService.CodeFlag knownFlags = 0; + for (var i = 0; i < config.Codes.Count; ++i) + { + using var id = ImUtf8.PushId(i); + var (code, state) = config.Codes[i]; + var (action, flag) = codeService.CheckCode(code); + if (flag is 0) + continue; + + var data = CodeService.GetData(flag); + + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, + $"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}", + !canDelete)) + { + action!(false); + config.Codes.RemoveAt(i--); + codeService.SaveState(); + } + + knownFlags |= flag; + ImUtf8.SameLineInner(); + if (ImUtf8.Checkbox("\0"u8, ref state)) + { + action!(state); + codeService.SaveState(); + } + + var hovered = ImGui.IsItemHovered(); + ImGui.SameLine(); + ImUtf8.Selectable(code, false); + hovered |= ImGui.IsItemHovered(); + DrawSource(i, code); + DrawTarget(i); + if (hovered) + { + using var tt = ImUtf8.Tooltip(); + ImUtf8.Text(data.Effect); + } + } + + return knownFlags; + } + + private void DrawSource(int idx, string code) + { + using var source = ImUtf8.DragDropSource(); + if (!source) + return; + + if (!DragDropSource.SetPayload(DragDropLabel)) + _dragCodeIdx = idx; + ImUtf8.Text($"Dragging {code}..."); + } + + private void DrawTarget(int idx) + { + using var target = ImUtf8.DragDropTarget(); + if (!target.IsDropping(DragDropLabel) || _dragCodeIdx == -1) + return; + + if (config.Codes.Move(_dragCodeIdx, idx)) + codeService.SaveState(); + _dragCodeIdx = -1; + } + + private void DrawCodeHints(CodeService.CodeFlag knownFlags) + { + if (knownFlags.HasFlag(CodeService.AllHintCodes)) + return; + + if (ImUtf8.Button(_showCodeHints ? "Hide Hints"u8 : "Show Hints"u8)) + _showCodeHints = !_showCodeHints; + + if (!_showCodeHints) + return; + + foreach (var code in Enum.GetValues()) + { + if (knownFlags.HasFlag(code)) + continue; + + var data = CodeService.GetData(code); + if (!data.Display) + continue; + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + ImUtf8.Text(data.Effect); + using var indent = ImRaii.PushIndent(2); + using (ImUtf8.Group()) + { + ImUtf8.Text("Capitalized letters: "u8); + ImUtf8.Text("Punctuation: "u8); + } + + ImUtf8.SameLineInner(); + using (ImUtf8.Group()) + { + ImUtf8.Text($"{data.CapitalCount}"); + ImUtf8.Text($"{data.Punctuation}"); + } + + ImUtf8.TextWrapped(data.Hint); + } + } + + + private static void DrawTooltip() + { + if (!ImGui.IsItemHovered()) + return; + + ImGui.SetNextWindowSize(new Vector2(400, 0)); + using var tt = ImUtf8.Tooltip(); + ImUtf8.TextWrapped(Tooltip); + } +} diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index e9ac60f..6259f06 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -7,8 +7,6 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using Glamourer.Services; -using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -19,16 +17,15 @@ namespace Glamourer.Gui.Tabs.SettingsTab; public class SettingsTab( Configuration config, DesignFileSystemSelector selector, - CodeService codeService, ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, - FunModule funModule, IKeyState keys, DesignColorUi designColorUi, PaletteImport paletteImport, PalettePlusChecker paletteChecker, - CollectionOverrideDrawer overrides) + CollectionOverrideDrawer overrides, + CodeDrawer codeDrawer) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -36,8 +33,6 @@ public class SettingsTab( public ReadOnlySpan Label => "Settings"u8; - private string _currentCode = string.Empty; - public void DrawContent() { using var child = ImRaii.Child("MainWindowChild"); @@ -57,7 +52,7 @@ public class SettingsTab( DrawInterfaceSettings(); DrawColorSettings(); overrides.Draw(); - DrawCodes(); + codeDrawer.Draw(); } MainWindow.DrawSupportButtons(changelog.Changelog); @@ -297,69 +292,6 @@ public class SettingsTab( ImGui.NewLine(); } - private void DrawCodes() - { - const string tooltip = - "Cheat Codes are not actually for cheating in the game, but for 'cheating' in Glamourer. They allow for some fun easter-egg modes that usually manipulate the appearance of all players you see (including yourself) in some way.\n\n" - + "Cheat Codes are generally pop culture references, but it is unlikely you will be able to guess any of them based on nothing. Some codes have been published on the discord server, but other than that, we are still undecided on how and when to publish them or add any new ones. Maybe some will be hidden in the change logs or on the help pages. Or maybe I will just add hints in this section later on.\n\n" - + "In any case, you are not losing out on anything important if you never look at this section and there is no real reason to go on a treasure hunt for them. It is mostly something I added because it was fun for me."; - - var show = ImGui.CollapsingHeader("Cheat Codes"); - if (ImGui.IsItemHovered()) - { - ImGui.SetNextWindowSize(new Vector2(400, 0)); - using var tt = ImRaii.Tooltip(); - ImGuiUtil.TextWrapped(tooltip); - } - - if (!show) - return; - - using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, _currentCode.Length > 0)) - { - var color = codeService.CheckCode(_currentCode) != null ? ColorId.ActorAvailable : ColorId.ActorUnavailable; - using var c = ImRaii.PushColor(ImGuiCol.Border, color.Value(), _currentCode.Length > 0); - if (ImGui.InputTextWithHint("##Code", "Enter Cheat Code...", ref _currentCode, 512, ImGuiInputTextFlags.EnterReturnsTrue)) - if (codeService.AddCode(_currentCode)) - _currentCode = string.Empty; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker(tooltip); - - DrawCodeHints(); - - if (config.Codes.Count <= 0) - return; - - for (var i = 0; i < config.Codes.Count; ++i) - { - var (code, state) = config.Codes[i]; - var action = codeService.CheckCode(code); - if (action == null) - continue; - - if (ImGui.Checkbox(code, ref state)) - { - action(state); - codeService.SaveState(); - } - } - - if (ImGui.Button("Who am I?!?")) - funModule.WhoAmI(); - - ImGui.SameLine(); - - if (ImGui.Button("Who is that!?!")) - funModule.WhoIsThat(); - } - - private void DrawCodeHints() - { - // TODO - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void Checkbox(string label, string tooltip, bool current, Action setter) { diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index fb7a55e..724576e 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -32,6 +32,9 @@ public class CodeService Dolphins = 0x080000, } + public static readonly CodeFlag AllHintCodes = + Enum.GetValues().Where(f => GetData(f).Display).Aggregate((CodeFlag)0, (f1, f2) => f1 | f2); + public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; @@ -81,7 +84,7 @@ public class CodeService var changes = false; for (var i = 0; i < _config.Codes.Count; ++i) { - var enabled = CheckCode(_config.Codes[i].Code); + var enabled = CheckCode(_config.Codes[i].Code).Item1; if (enabled == null) { _config.Codes.RemoveAt(i--); @@ -100,7 +103,7 @@ public class CodeService public bool AddCode(string name) { - if (CheckCode(name) == null || _config.Codes.Any(p => p.Code == name)) + if (CheckCode(name).Item1 is null || _config.Codes.Any(p => p.Code == name)) return false; _config.Codes.Add((name, false)); @@ -108,16 +111,14 @@ public class CodeService return true; } - public Action? CheckCode(string name) + public (Action?, CodeFlag) CheckCode(string name) { var flag = GetCode(name); if (flag == 0) - return null; + return (null, 0); var badFlags = ~GetMutuallyExclusive(flag); - return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag; - - ; + return (v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag, flag); } public CodeFlag GetCode(string name) @@ -206,29 +207,29 @@ public class CodeService _ => [], }; - private static (int CapitalCount, string Punctuation, string Hint, string Effect) GetData(CodeFlag flag) + public static (bool Display, int CapitalCount, string Punctuation, string Hint, string Effect) GetData(CodeFlag flag) => flag switch { - CodeFlag.Clown => (2, ",.", "", "Randomizes dyes for every player."), - CodeFlag.Emperor => (1, ".", "", "Randomizes clothing for every player."), - CodeFlag.Individual => (2, "'!'!", "", "Randomizes customizations for every player."), - CodeFlag.Dwarf => (1, "!", "", "Sets the player character to minimum height and all other players to maximum height."), - CodeFlag.Giant => (2, "!", "", "Sets the player character to maximum height and all other players to minimum height."), - CodeFlag.OopsHyur => (1, "','.", "", "Turns all players to Hyur."), - CodeFlag.OopsElezen => (1, ".", "", "Turns all players to Elezen."), - CodeFlag.OopsLalafell => (2, ",!", "", "Turns all players to Lalafell."), - CodeFlag.OopsMiqote => (3, ".", "", "Turns all players to Miqo'te."), - CodeFlag.OopsRoegadyn => (1, "!", "", "Turns all players to Roegadyn."), - CodeFlag.OopsAuRa => (1, "',.", "", "Turns all players to Au Ra."), - CodeFlag.OopsHrothgar => (1, "',...", "", "Turns all players to Hrothgar."), - CodeFlag.OopsViera => (2, "!'!", "", "Turns all players to Viera."), - CodeFlag.Artisan => (3, ",,!", "", "Enable a debugging mode for the UI. Not really useful."), - CodeFlag.SixtyThree => (2, "", "", "Inverts the gender of every player."), - CodeFlag.Shirts => (1, "-.", "", "Highlights all items in the Unlocks tab as if they were unlocked."), - CodeFlag.World => (1, ",.", "", "Sets every player except the player character themselves to job-appropriate gear."), - CodeFlag.Elephants => (1, "!", "", "Sets every player to the elephant costume in varying shades of pink."), - CodeFlag.Crown => (1, ".", "", "Sets every player with a mentor symbol enabled to the clown's hat."), - CodeFlag.Dolphins => (5, ",", "", "Sets every player to a Namazu hat with different costume bodies."), - _ => (0, string.Empty, string.Empty, string.Empty), + CodeFlag.Clown => (true, 3, ",.", "A punchline uttered by Rorschach.", "Randomizes dyes for every player."), + CodeFlag.Emperor => (true, 1, ".", "A truth that only a child can see.", "Randomizes clothing for every player."), + CodeFlag.Individual => (true, 2, "'!'!", "Something an unwilling prophet tries to convince his followers of.", "Randomizes customizations for every player."), + CodeFlag.Dwarf => (true, 1, "!", "A centuries old metaphor about humility and the progress of science.", "Sets the player character to minimum height and all other players to maximum height."), + CodeFlag.Giant => (true, 2, "!", "A Swift renaming of one of the most famous literary openings of all time.", "Sets the player character to maximum height and all other players to minimum height."), + CodeFlag.OopsHyur => (true, 1, "','.", "An alkaline quote attributed to Marilyn Monroe.", "Turns all players to Hyur."), + CodeFlag.OopsElezen => (true, 1, ".", "A line from a Futurama song about the far future.", "Turns all players to Elezen."), + CodeFlag.OopsLalafell => (true, 2, ",!", "The name of a discontinued plugin.", "Turns all players to Lalafell."), + CodeFlag.OopsMiqote => (true, 3, ".", "A Sandman story.", "Turns all players to Miqo'te."), + CodeFlag.OopsRoegadyn => (true, 2, "!", "A line from a Steven Universe song about his desires.", "Turns all players to Roegadyn."), + CodeFlag.OopsAuRa => (true, 1, "',.", "Something a plumber hates to hear, made to something a scaly hates to hear and initial Au Ra designs.", "Turns all players to Au Ra."), + CodeFlag.OopsHrothgar => (true, 1, "',...", "A meme about the attractiveness of anthropomorphic animals.", "Turns all players to Hrothgar."), + CodeFlag.OopsViera => (true, 2, "!'!", "A panicked exclamation about bunny arithmetics.", "Turns all players to Viera."), + CodeFlag.SixtyThree => (true, 2, "", "The title of a famous LGBTQ-related french play and movie.", "Inverts the gender of every player."), + CodeFlag.Shirts => (true, 2, "-.", "A pre-internet meme about disappointing rewards for an adventure, adapted to this specific cheat code.", "Highlights all items in the Unlocks tab as if they were unlocked."), + CodeFlag.World => (true, 1, ",.", "A quote about being more important than other people.", "Sets every player except the player character themselves to job-appropriate gear."), + CodeFlag.Elephants => (true, 1, "!", "Appropriate lyrics that can also be found in Glamourer's changelogs.", "Sets every player to the elephant costume in varying shades of pink."), + CodeFlag.Crown => (true, 1, ".", "A famous Shakespearean line.", "Sets every player with a mentor symbol enabled to the clown's hat."), + CodeFlag.Dolphins => (true, 5, ",", "The farewell of the second smartest species on Earth.", "Sets every player to a Namazu hat with different costume bodies."), + CodeFlag.Artisan => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } diff --git a/OtterGui b/OtterGui index 1d93651..0b5afff 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 1d9365164655a7cb38172e1311e15e19b1def6db +Subproject commit 0b5afffda19d3e16aec9e8682d18c8f11f67f1c6 From 87d51c04ad143d992e086d67557ff0563b53b881 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 May 2024 16:58:25 +0200 Subject: [PATCH 43/45] Add changelog. --- Glamourer/Gui/GlamourerChangelog.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index b91d642..4949fba 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -32,7 +32,8 @@ public class GlamourerChangelog AddDummy(Changelog); Add1_2_0_0(Changelog); Add1_2_1_0(Changelog); - Add1_2_2_0(Changelog); + AddDummy(Changelog); + Add1_2_3_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -53,8 +54,8 @@ public class GlamourerChangelog } } - private static void Add1_2_2_0(Changelog log) - => log.NextVersion("Version 1.2.2.0") + private static void Add1_2_3_0(Changelog log) + => log.NextVersion("Version 1.2.3.0") .RegisterHighlight("Added a field to rename designs directly from the mod selector context menu, instead of moving them in the filesystem.") .RegisterEntry("You can choose which rename field (none, either one or both) to display in the settings.", 1) .RegisterEntry("Automatically applied offhand weapons due to mainhand settings now also apply the mainhands dye.") @@ -65,6 +66,8 @@ public class GlamourerChangelog .RegisterHighlight("Added a button to overwrite the selected design with the current player state.") .RegisterEntry("Added some copy/paste functionality for mod associations.") .RegisterEntry("Reworked the API and IPC structure heavily.") + .RegisterEntry("Added warnings if Glamourer can not attach successfully to Penumbra or if Penumbras IPC version is not correct.") + .RegisterEntry("Added hints for all of the available cheat codes and improved the cheat code display somewhat.") .RegisterEntry("Fixed weapon selectors not having a favourite star available.") .RegisterEntry("Fixed issues with items with custom names.") .RegisterEntry("Fixed the labels for eye colors.") From 9851533143495d15f7bbc7ba75e0fbdbab1cfae0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 May 2024 16:59:39 +0200 Subject: [PATCH 44/45] Update GameData --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index b828297..29b71cf 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit b8282970ee78a2c085e740f60450fecf7ea58b9c +Subproject commit 29b71cf7b3cc68995d38f0954fa38c4b9500a81d From 8ce667e7e4fff7158194c90f605ba58ac5711c11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 May 2024 23:02:39 +0200 Subject: [PATCH 45/45] improve a code. --- Glamourer/Gui/GlamourerChangelog.cs | 15 ++++++++++----- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 1 + Glamourer/Services/CodeService.cs | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 4949fba..e9173ec 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -56,13 +56,16 @@ public class GlamourerChangelog private static void Add1_2_3_0(Changelog log) => log.NextVersion("Version 1.2.3.0") - .RegisterHighlight("Added a field to rename designs directly from the mod selector context menu, instead of moving them in the filesystem.") + .RegisterHighlight( + "Added a field to rename designs directly from the mod selector context menu, instead of moving them in the filesystem.") .RegisterEntry("You can choose which rename field (none, either one or both) to display in the settings.", 1) .RegisterEntry("Automatically applied offhand weapons due to mainhand settings now also apply the mainhands dye.") .RegisterHighlight("Added a height display in real-world units next to the height-selector.") .RegisterEntry("This can be configured to use your favourite unit of measurement, even wrong ones, or not display at all.", 1) - .RegisterHighlight("Added a chat command '/glamour applycustomization' that can apply single customization values to actors. Use without arguments for help.") - .RegisterHighlight("Added an option for designs to always force a redraw when applied to a character, regardless of whether it is necessary or not.") + .RegisterHighlight( + "Added a chat command '/glamour applycustomization' that can apply single customization values to actors. Use without arguments for help.") + .RegisterHighlight( + "Added an option for designs to always force a redraw when applied to a character, regardless of whether it is necessary or not.") .RegisterHighlight("Added a button to overwrite the selected design with the current player state.") .RegisterEntry("Added some copy/paste functionality for mod associations.") .RegisterEntry("Reworked the API and IPC structure heavily.") @@ -81,9 +84,11 @@ public class GlamourerChangelog => log.NextVersion("Version 1.2.1.0") .RegisterEntry("Updated for .net 8 and FFXIV 6.58, using some new framework options to improve performance and stability.") .RegisterEntry("Previewing changed items in Penumbra now works with all weapons in GPose. (1.2.0.8)") - .RegisterEntry("Added a design type selectable for automation that applies the design currently selected in the quick design bar. (1.2.0.4)") + .RegisterEntry( + "Added a design type selectable for automation that applies the design currently selected in the quick design bar. (1.2.0.4)") .RegisterEntry("Added an option to respect manual changes when changing automation settings. (1.2.0.3)") - .RegisterEntry("You can now apply designs to the player character with a double click on them (can be turned off in settings). (1.2.0.1)") + .RegisterEntry( + "You can now apply designs to the player character with a double click on them (can be turned off in settings). (1.2.0.1)") .RegisterEntry("The last selected design and tab are now stored and applied on startup. (1.2.0.1)") .RegisterEntry("Fixed behavior of revert to automation to actually revert and not just reapply. (1.2.0.8)") .RegisterEntry("Added Reapply Automation buttons and chat commands with prior behaviour.", 1) diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index bf9e5cf..e49b8d1 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -173,6 +173,7 @@ public class CodeDrawer(Configuration config, CodeService codeService, FunModule ImUtf8.SameLineInner(); using (ImUtf8.Group()) { + using var mono = ImRaii.PushFont(UiBuilder.MonoFont); ImUtf8.Text($"{data.CapitalCount}"); ImUtf8.Text($"{data.Punctuation}"); } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 724576e..7d513dd 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -211,7 +211,7 @@ public class CodeService => flag switch { CodeFlag.Clown => (true, 3, ",.", "A punchline uttered by Rorschach.", "Randomizes dyes for every player."), - CodeFlag.Emperor => (true, 1, ".", "A truth that only a child can see.", "Randomizes clothing for every player."), + CodeFlag.Emperor => (true, 1, ".", "A truth about clothes that only a child will speak.", "Randomizes clothing for every player."), CodeFlag.Individual => (true, 2, "'!'!", "Something an unwilling prophet tries to convince his followers of.", "Randomizes customizations for every player."), CodeFlag.Dwarf => (true, 1, "!", "A centuries old metaphor about humility and the progress of science.", "Sets the player character to minimum height and all other players to maximum height."), CodeFlag.Giant => (true, 2, "!", "A Swift renaming of one of the most famous literary openings of all time.", "Sets the player character to maximum height and all other players to minimum height."),